I am trying to translate the x and y of each data point on the linear chart according to their position but I am always getting the position of the last item in the animation and if I try to save positions inside the component using usestate of parent, animations doesn't work correctly
const chart = useRef(null)
const dot = useRef()
const ax = new Animated.Value(0)
const ay = new Animated.Value(0)
const [pos, setPos] = useState([])
const [animateIndex, setAnimateIndex] = useState(0)
useEffect(() => {
dot.current.animateDot()
}, [animateIndex])
<LineChart
ref={chart}
data={{
datasets: [
{
data: [
3.5, 5, 5.5, 6.5, 7.2, 8, 8.7
]
}
]
}}
renderDotContent={({ x, y, index, }) =>
<Dot //want to save x and y of each dot
ref={dot}
x={x}
y={y}
index={index}
key={index}
ax={ax}
ay={ay}
setPos={setPos}
pos={pos}
animateIndex={animateIndex}
hiden={hiden}
setHiden={setHiden} />
}
xLabelsOffset={-8}
width={SIZES.width * 0.84}
height={SIZES.height * 0.20}
yAxisSuffix="k"
withVerticalLines={false}
yAxisInterval={1}
/>
dot component
const Dot = ({ x, y, index, hiden, setHiden, ax, ay, animateIndex, setPos, pos }, ref) => {
useImperativeHandle(ref, () => ({
// methods connected to `ref`
animateDot: () => { animateDot() }
}))
const animateDot = () => {
console.log('animate dot function', index, posi)
Animated.timing(ax, {
toValue: SIZES.width / 1.3 - x,
duration: 1000,
useNativeDriver: true
}).start()
Animated.timing(ay, {
toValue: -SIZES.height / 2 + y,
duration: 1000,
useNativeDriver: true
}).start()
}
return (
<Animated.View
style={{
position: 'absolute',
top: y - 7.5,
left: x - 7.5,
height: 15,
width: 15,
borderRadius: 10,
transform: [
{
translateY: (animateIndex === index) ? ay : 0
},
{
translateX: (animateIndex === index) ? ax : 0
}
],
borderWidth: 3.5,
borderColor: COLORS.primary,
backgroundColor: COLORS.secondary
}}>
</Animated.View>
)
}
export default forwardRef(Dot)
how can i save positions of each item
I have an animation that uses Animated.timing() which slides a component in to view when the condition mapIsCentered = true. When the condition isn't met the component just disappears ungracefully. I'd like for it to slide in and out as the condition changes.
One thing to note the mapIsCentered state is updated on a different screen and passed as a prop to the component I am working in. I have logged the state and it updates when the map is moved.
** the slide in works as expected
Thanks to #Ashwith for the first answer
const values = useRef(new Animated.ValueXY({ x: 0, y: 120 })).current;
useEffect(() => {
Animated.timing(values, {
toValue: mapIsCentered ? { x: 0, y: 0 } : { x: 0, y: 120 },
duration: 500,
useNativeDriver: false,
}).start();
}, [mapIsCentered]);
{!walkInProgress && !hasOnGoingWalks && (
<Animated.View
style={{
transform: [{ translateY: values.y }],
}}
>
<WeatherToast
translations={translations}
loading={loading}
weather={weather}
/>
</Animated.View>
Thanks in advance!
I have changed the structure hope it works for you...
snack: https://snack.expo.io/#ashwith00/excited-orange
App.js
const walkInProgress = false , hasOnGoingWalks = false;
export default function App() {
const { width } = useWindowDimensions();
const [mapCentered, setMapCentered] = React.useState(false)
const toggle = () => {
setMapCentered((ct) => !ct);
};
return (
<View style={styles.container}>
<WeatherToast mapCentered={mapCentered && !walkInProgress && !hasOnGoingWalks} />
<Button title="shift" onPress={toggle} />
</View>
);
}
WeatherTost.js
export default ({ mapCentered }) => {
const [visible, setVisible] = useState(mapCentered);
const { width } = useWindowDimensions();
const values = React.useRef(new Animated.ValueXY({ x: 0, y: 120 })).current;
React.useEffect(() => {
if (mapCentered) {
setVisible(true);
Animated.timing(values, {
toValue: { x: 0, y: 0 },
duration: 300,
}).start();
} else {
Animated.timing(values, {
toValue: { x: width, y: 0 },
duration: 300,
}).start(({ finished }) => {
if (finished) {
setVisible(false);
}
});
}
}, [mapCentered]);
const styles = [];
return visible ? (
<Animated.View
style={{
width: 200,
height: 200,
position: 'absolute',
backgroundColor: 'red',
transform: [
{
translateX: values.x,
},
{
translateY: values.y,
},
],
}}
/>
) : (
<View />
);
};
I am trying to make an image slider that autoscrolls horizontally with react native scrollview, it returns error can't find error _scrollView. Who knows a fix or a better to go about it.
Right I have to manually move between the images.I tried adding a ref to the scrollView, but the ref seems to be giving error.
import React, { Component } from 'react'
import { Animated, View, StyleSheet, Image, Dimensions, ScrollView } from 'react-native'
const deviceWidth = Dimensions.get('window').width
const deviceHeight = Dimensions.get('window').height
const FIXED_BAR_WIDTH = 280
const BAR_SPACE = 10
const images = [
require("../../assets/images/banner_1.jpg"),
require("../../assets/images/banner2.jpg")
]
export default class ImgSlider extends Component {
_scrollView = React.createRef();
componentDidMount() {
const numOfBackground = 2;
let scrollValue = 0, scrolled = 0;
setInterval(function () {
scrolled++;
if(scrolled < numOfBackground)
scrollValue = scrollValue + deviceWidth;
else{
scrollValue = 0;
scrolled = 0
}
_scrollView.scrollTo({ x: scrollValue, animated: false })
}, 3000);
}
numItems = images.length
itemWidth = (FIXED_BAR_WIDTH / this.numItems) - ((this.numItems - 1) * BAR_SPACE)
animVal = new Animated.Value(0)
render() {
let imageArray = []
let barArray = []
images.forEach((image, i) => {
console.log(image, i)
const thisImage = (
<Image
key={`image${i}`}
source={image}
style={{ width: deviceWidth, height: 300 }}
/>
)
imageArray.push(thisImage)
const scrollBarVal = this.animVal.interpolate({
inputRange: [deviceWidth * (i - 1), deviceWidth * (i + 1)],
outputRange: [-this.itemWidth, this.itemWidth],
extrapolate: 'clamp',
})
const thisBar = (
<View
key={`bar${i}`}
style={[
styles.track,
{
width: this.itemWidth,
marginLeft: i === 0 ? 0 : BAR_SPACE,
},
]}
>
<Animated.View
style={[
styles.bar,
{
width: this.itemWidth,
transform: [
{ translateX: scrollBarVal },
],
},
]}
/>
</View>
)
barArray.push(thisBar)
})
return (
<View
style={styles.container}
flex={1}
>
<ScrollView
horizontal
ref={this._scrollView}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={10}
pagingEnabled
// ref={(scrollView) => { _scrollView = scrollView }}
onScroll={
Animated.event(
[{ nativeEvent: { contentOffset: { x: this.animVal } } }]
)
}
>
{imageArray}
</ScrollView>
<View
style={styles.barContainer}
>
{barArray}
</View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
barContainer: {
position: 'absolute',
zIndex: 2,
top: 290,
flexDirection: 'row',
},
track: {
backgroundColor: '#ccc',
overflow: 'hidden',
height: 2,
},
bar: {
backgroundColor: '#BB1E18',
height: 2,
position: 'absolute',
left: 0,
top: 0,
},
})
this._scrollView does not have the function scrollTo try console logging whats init and see whats going on in there.
I wanna render array of "Star" to the view with absolute position. When I add them 1 by 1. It works fine, but when I add them as array, it only shown the last View in the array
render(){
const pointSize = 30
let pointViews = []
let baseStyle = {position:'absolute',width:pointSize,height:pointSize,backgroundColor:'yellow',borderRadius:pointSize/2}
console.log("render ");
let points = this.state.data
this.state.data.forEach(function(point){
console.log("for: "+JSON.stringify(point));
let finalStyle = baseStyle
finalStyle.left = point.x
finalStyle.top = point.y
let key = point.x+"_"+point.y
let pointView = <View style={finalStyle} key={key}><Text>{key}</Text></View>
pointViews.push(pointView)
})
console.log("pointViewss: "+pointViews)
return(
<Container style={{position:'absolute'}}>
{points.map((point,key) => {
let finalStyle = baseStyle
finalStyle.left = point.x
finalStyle.top = point.y
return(
<View style={finalStyle} key={key}><Text>{key}</Text></View>
)
})}
{/*<View style={[baseStyle,{left:10,top:10}]}></View>*/}
{/*<View style={[baseStyle,{left:50,top:50}]}></View>*/}
{/*<View style={[baseStyle,{left:100,top:100}]}></View>*/}
</Container>
)
}
What it shown
What I want
Here you go
constructor(props){
super(props)
this.state= {
data: [
{x: 10, y:10},
{ x: 50, y: 50 },
{ x: 100, y: 100 }
]
}
}
render() {
const pointSize = 30
let pointViews = []
let baseStyle = { position: 'absolute', width: pointSize, height: pointSize, backgroundColor: 'yellow', borderRadius: pointSize / 2 }
return (
<View style={{position: 'absolute'}}>
{
this.state.data.map((point, key) => {
return (
<View style={{ ...baseStyle, left: point.x, top: point.y }} key={key}><Text>{key}</Text></View>
)
})
}
</View>
)
}
I hope its clear enough :)
Hopefully you can help me with a bug I'm having a bit of bother sorting out. I'm working on a bug in an app built using React Native. It is building to IOS and Android. I have a ScrollView in a component that contains cards that are draggable objects.
These cards are dragged from the ScrollView they are in, up to buckets at the top of the screen. They disappear from the ScrollView and the remaining ones get reorganised so they stay ordered and neat. That works fine, you press on a box in the list and drag it to the buckets.
There is a bit of whitespace above the list of cards in the ScrollView. The ScrollView functionality works when swiping within this whitespace above the boxes, but I can't swipe on the boxes themselves without it beginning to drag the card.
Here is the component itself:
import React, { Component } from 'react';
import { StyleSheet, Text, View, ScrollView, Dimensions, Alert } from 'react-native';
import { connect } from 'react-redux';
import * as ConstStyles from '../../Consts/styleConsts';
import Bucket from '../Partials/bucketContainers';
import BusyIndicator from 'react-native-busy-indicator';
import loaderHandler from 'react-native-busy-indicator/LoaderHandler';
import CatCard from '../Partials/categoryCard';
import * as FeedActions from '../../../Redux/Feeds/actions';
import * as AuthFunctions from '../../Auth/functions';
export class SetupLikes extends Component {
static navigatorStyle = ConstStyles.standardNav;
constructor(props) {
super(props);
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
this.card = [];
let button = {
leftButtons: [
{
title: 'Reset',
id: 'reset'
}
],
rightButtons: [
{
title: this.props.newAccount ? 'Go' : 'Done',
id: this.props.newAccount ? '' : 'skip',
disabled: this.props.newAccount
}
]
};
this.props.navigator.setButtons(button);
}
state = {
xOffset: 0,
positions: [],
placedCards: [],
loves: [],
okays: [],
hates: []
};
onNavigatorEvent(event) {
setTimeout(async () => {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'skip') {
this.props.navigator.dismissModal({
animationType: 'slide-down'
});
} else if (event.id === 'reset') {
await this.imgTap();
} else if (event.id === 'go') {
await this.setInterests();
}
}
}, 0);
}
async setInterests() {
loaderHandler.showLoader('Setting your interests...');
let newInterests = [];
this.state.loves.forEach(function(element) {
let cat = this.props.Feeds.categories[element];
let newItem = {
categoryid: cat.id,
sentimentid: 1
};
newInterests.push(newItem);
}, this);
this.state.okays.forEach(function(element) {
let cat = this.props.Feeds.categories[element];
let newItem = {
categoryid: cat.id,
sentimentid: 0
};
newInterests.push(newItem);
}, this);
this.state.hates.forEach(function(element) {
let cat = this.props.Feeds.categories[element];
let newItem = {
categoryid: cat.id,
sentimentid: -1
};
newInterests.push(newItem);
}, this);
let sesId = this.props.User.sessionId;
try {
await this.props.dispatch(FeedActions.setMyInterests(sesId, newInterests));
loaderHandler.hideLoader();
} catch (err) {
loaderHandler.hideLoader();
Alert.alert('Uh oh', 'Something went wrong. Please try again later');
return;
}
await AuthFunctions.setupAppLogin(this.props.dispatch, sesId);
}
async imgTap() {
await this.setState({ placedCards: [], loves: [], okays: [], hates: [], positions: [] });
setTimeout(() => {
let cntr = 0;
this.card.forEach(function(element) {
cntr++;
if (this.state.placedCards.includes(cntr - 1)) return;
if (element) element.snapTo({ index: 0 });
}, this);
}, 5);
this.props.navigator.setButtons({
rightButtons: [
{
title: 'Go',
id: '',
disabled: true
}
],
animated: true
});
}
cardPlaced(id, droppedIndex) {
let newList = this.state.placedCards;
newList.push(id);
let cntr = 0;
let offset = 0;
let newPosIndex = [];
this.props.Feeds.categories.forEach(cats => {
let posY = (offset % 2) * -120 - 20;
let xOffset = Math.floor(offset / 2);
let posX = xOffset * 105 + 10;
newPosIndex[cntr] = {
x: posX,
y: posY,
offset: offset % 2
};
if (!newList.includes(cntr)) offset++;
cntr++;
});
if (droppedIndex === 1) {
let newLoves = this.state.loves;
newLoves.push(id);
this.setState({
loves: newLoves,
placedCards: newList,
positions: newPosIndex
});
} else if (droppedIndex === 2) {
let newOkays = this.state.okays;
newOkays.push(id);
this.setState({
okays: newOkays,
placedCards: newList,
positions: newPosIndex
});
} else if (droppedIndex === 3) {
let newHates = this.state.hates;
newHates.push(id);
this.setState({
hates: newHates,
placedCards: newList,
positions: newPosIndex
});
}
}
reShuffle() {
let cntr = 0;
this.card.forEach(function(element) {
cntr++;
if (this.state.placedCards.includes(cntr - 1)) return;
if (element) element.snapTo({ index: 0 });
}, this);
}
setButton() {
this.props.navigator.setButtons({
rightButtons: [
{
title: this.props.newAccount ? 'Go' : 'Done',
id: 'go'
}
],
animated: true
});
}
onChangeSize(scrollWidth, scrollHeight) {
let { height, width } = Dimensions.get('window');
let farRight = this.state.xOffset + width;
if (farRight > scrollWidth && farRight > 0) {
let xOffset = scrollWidth - width;
this.setState({ xOffset });
}
}
onSnap(index, id) {
this.cardPlaced(id, index);
this.reShuffle();
this.setButton();
if (this.props.Feeds.categories.length === this.state.placedCards.length)
setTimeout(async () => {
await this.setInterests();
}, 1);
}
renderCats() {
let cntr = 0;
var { height, width } = Dimensions.get('window');
let res = this.props.Feeds.categories.map(item => {
let ptr = cntr;
let posY = (cntr % 2) * -120 - 20;
let xOffset = Math.floor(cntr / 2);
let posX = xOffset * 105 + 10;
let vertPos = posY - 200 + ((cntr + 1) % 2) * -120;
posX = this.state.positions[ptr] ? this.state.positions[ptr].x : posX;
posY = this.state.positions[ptr] ? this.state.positions[ptr].y : posY;
let off = this.state.positions[ptr] ? this.state.positions[ptr].offset : ptr % 2;
cntr++;
if (this.state.placedCards.includes(cntr - 1)) return null;
item.key = cntr;
return (
<CatCard
key={ptr}
item={item}
ptr={ptr}
cntr={cntr}
xOffset={this.state.xOffset}
odd={off}
posX={posX}
posY={posY}
yDrop={vertPos}
screenWidth={width}
onSnap={(res, id) => this.onSnap(res, id)}
gotRef={ref => (this.card[ptr] = ref)}
/>
);
});
cntr = 0;
res.forEach(ele => {
if (ele !== null) ele.key = cntr++;
});
let test = this.props.Feeds.categories[0];
return res;
}
onScroll(res) {
this.setState({ xOffset: res.nativeEvent.contentOffset.x });
}
render() {
let colWidth = Math.ceil((this.props.Feeds.categories.length - this.state.placedCards.length) / 2) * 106;
return (
<View style={styles.container}>
<View style={styles.bucketContainer1}>
<Bucket
type={'Love'}
imageToUse={require('../../../img/waveLove.png')}
height={this.state.loveHeight}
count={this.state.loves.length}
backgroundColor={'rgb(238, 136, 205)'}
/>
</View>
<View style={styles.bucketContainer2}>
<Bucket
type={'OK'}
imageToUse={require('../../../img/waveOkay.png')}
height={this.state.okayHeight}
count={this.state.okays.length}
backgroundColor={'rgb(250, 179, 39)'}
/>
</View>
<View style={styles.bucketContainer3}>
<Bucket
type={'Dislike'}
imageToUse={require('../../../img/waveHate.png')}
height={this.state.hateHeight}
count={this.state.hates.length}
backgroundColor={'rgb(112, 127, 208)'}
/>
</View>
<View style={styles.descriptionContainer}>
<Text style={styles.dragLikesTitle}>Drag Likes</Text>
<View style={styles.dividingLine} />
<View>
<Text style={styles.descriptionText}>Drag your likes and dislikes into the bucket above,</Text>
<Text style={styles.descriptionText}>so we can generate your profile!</Text>
</View>
</View>
<ScrollView
ref={ref => (this.scroller = ref)}
onMomentumScrollEnd={res => this.onScroll(res)}
style={styles.scroller}
horizontal={true}
onContentSizeChange={(width, height) => this.onChangeSize(width, height)}
>
<View style={[styles.insideView, { width: colWidth }]}>{this.renderCats()}</View>
</ScrollView>
<BusyIndicator size={'large'} overlayHeight={120} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignSelf: 'stretch',
alignItems: 'center',
backgroundColor: 'white'
},
bucketContainer1: {
position: 'absolute',
height: 130,
width: 95,
left: 10,
top: 5
},
bucketContainer2: {
position: 'absolute',
height: 130,
width: 95,
top: 5
},
bucketContainer3: {
position: 'absolute',
height: 130,
width: 95,
right: 10,
top: 5
},
insideView: {
width: 2500,
justifyContent: 'flex-end',
overflow: 'visible'
},
cardContainer: {
borderWidth: 1,
borderColor: 'rgb(200,200,200)',
borderRadius: 4,
alignItems: 'center',
width: 100,
backgroundColor: 'white'
},
catImage: {
height: 100,
width: 100,
borderTopRightRadius: 4,
borderTopLeftRadius: 4
},
button: {
backgroundColor: 'rgba(255,0,0,0.2)',
width: 90,
height: 50
},
scroller: {
height: '100%',
width: '100%',
overflow: 'visible'
},
card: {
position: 'absolute',
overflow: 'visible'
},
descriptionContainer: {
top: 140,
width: '100%',
alignItems: 'center',
position: 'absolute'
},
dividingLine: {
height: 1,
width: '100%',
borderWidth: 0.5,
borderColor: 'rgb(150,150,150)',
marginBottom: 5
},
dragLikesTitle: {
fontFamily: 'Slackey',
fontSize: 20,
color: 'rgb(100,100,100)'
},
descriptionText: {
fontSize: 12,
textAlign: 'center',
marginTop: 5
}
});
function mapStateToProps(state) {
return {
User: state.User,
Feeds: state.Feeds
};
}
export default connect(mapStateToProps)(SetupLikes);
Down at the bottom of the render function is where you'll see the ScrollView. It's rendering the categories via a function called renderCats.
It may be that because the cards I am rendering are draggable, that fixing this is an impossibility but I thought I would see if anyone has a better idea of how this may be fixed!
EDIT TO INCLUDE CatCard component...
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
ScrollView,
TouchableOpacity,
FlatList,
Image,
Platform,
Animated,
Easing,
Dimensions
} from 'react-native';
import * as Consts from '../../Consts/colourConsts';
import * as ConstStyles from '../../Consts/styleConsts';
import PropTypes from 'prop-types';
import Interactable from 'react-native-interactable';
import { CachedImage } from 'react-native-img-cache';
class CatCard extends Component {
state = {
initX: this.props.posX,
initY: this.props.posY,
zIndex: 1
};
onSnap(res, point) {
if (res.nativeEvent.index === 0) {
return;
}
let index = res.nativeEvent.index;
setTimeout(() => {
this.props.onSnap(index, point);
let end = new Date();
}, 100);
Animated.timing(this.opacity, {
toValue: 0,
duration: 100,
useNativeDriver: true
}).start();
}
constructor(props) {
super(props);
this.opacity = new Animated.Value(1);
this.height = Dimensions.get('window').height;
}
gotRef(ref) {
this.props.gotRef(ref);
}
render() {
let upY = this.props.posY + this.height + (1 - this.props.odd) * -120;
upY = upY * -1;
upY += 50;
return (
<Interactable.View
ref={ref => {
this.gotRef(ref);
}}
onSnap={res => this.onSnap(res, this.props.ptr)}
style={[styles.card, { zIndex: this.state.zIndex }]}
animatedNativeDriver={true}
dragToss={0.01}
snapPoints={[
{
x: this.props.posX,
y: this.props.posY,
damping: 0.7,
tension: 300,
id: '0'
},
{
x: this.props.xOffset + 10,
y: upY,
tension: 30000,
damping: 0.1
},
{
x: this.props.xOffset + 10 + this.props.screenWidth * 0.33,
y: upY,
tension: 30000,
damping: 0.1
},
{
x: this.props.xOffset + 10 + this.props.screenWidth * 0.66,
y: upY,
tension: 30000,
damping: 0.1
}
]}
initialPosition={{ x: this.state.initX, y: this.state.initY }}
>
<Animated.View
style={[styles.cardContainer, { opacity: this.opacity }]}
>
<CachedImage
source={{ uri: this.props.item.imageUrl }}
style={styles.catImage}
/>
<Text style={styles.cardText}>{this.props.item.name}</Text>
</Animated.View>
</Interactable.View>
);
}
}
CatCard.PropTypes = {
count: PropTypes.any.isRequired,
type: PropTypes.string.isRequired,
imageToUse: PropTypes.any.isRequired,
height: PropTypes.object.isRequired,
backgroundColor: PropTypes.string.isRequired
};
const styles = StyleSheet.create({
card: {
position: 'absolute',
overflow: 'visible'
},
cardContainer: {
borderWidth: 1,
borderColor: 'rgb(200,200,200)',
borderRadius: 4,
alignItems: 'center',
width: 100,
backgroundColor: 'white'
},
cardText: {
fontFamily: 'Slackey',
fontSize: 10
},
catImage: {
height: 100,
width: 98,
borderTopRightRadius: 4,
borderTopLeftRadius: 4
}
});
export default CatCard;
Without seeing CatCard it is hard to know how the dragging is implemented. If you are doing raw PanResponder then you'll need to keep a something in state that keeps track of whether the ScrollView is scrolling and pass that down as a prop to CatCard which would then disallow the drag if the prop were true.
Alternatively, I'd suggest using react-native-interactable. It's a little to wrap your head around but it's a great abstraction from PanResponder. They have loads of examples and I have used it to make a swipeable list item that worked great, even with touchables inside the item.