How can I Animate instantly in React native - react-native

I am starting animated using 'onPanResponderMove' to move the animated view whenever the user is holding down the button
onPanResponderMove: (evt, gestureState) => {
if (withinbounds(gestureState.moveX, xOffset + buttonSize, xOffset + buttonSize * 2)) {
this.setState({y: this.state.y + 1});
Animated.timing(
this.state.layerOffset,
{toValue: {x: 0, y: (this.state.y * gridSize}, easing: Easing.in(Easing.ease)},
).start();
}
}
Which is being used to set the offset of a view
<Animated.View style={{position: 'absolute', left: this.state.layerOffset.x, top: this.state.layerOffset.y}}>
<Grid />
</Animated.View>
However, the way this is currently working, the animated view will not move until The button is stopped being press, how can I make the animation start immediately, instead of waiting for the button to no-longer be pressed although this.state.y is continually updated.

When playing around with PanResponder the way to go is usually using Animated.event instead of Animated.timing. Here's some doc that might help you out.
When using Animated.timing you should provide a duration, which you haven't done in this case. The difference between the two is that Animated.timing will change a value over a certain period of time whereas Animated.event instantly sets the value which is what you want.

Related

How to measure starting and ending 'x' and 'y' co-ordinates of a View in React Native

I want to move a circle from top to bottom inside of a View. For top I simply figured out y = 0.
How can I measure the y co-ordinate for the end of a view.
I tried Dimensions, but the height and width are different from coordinates.
My app uses Animated.ValueXY({ x: xCordinate, y: yCordinate })
The xCordinate and yCordinate get passed from a function that changes coordinates.
Animated.timing(this.moveAnimation, {
duration: 1000,
toValue: {x: xCordinate, y: yCordinate},
}).start();
I managed random value for my phone but it is not same for all devices
React native provides a onLayout method that gets triggered every time your view renders. Here's a sample code.
onLayout = (e) => {
this.setState({
width: e.nativeEvent.layout.width,
height: e.nativeEvent.layout.height,
x: e.nativeEvent.layout.x,
y: e.nativeEvent.layout.y
})
}
render (
<View onLayout={this.onLayout}>
<Text>Hamza Waleed</Text>
</View>
)
}

react-native: Animate position of Animated.View during onPanMove

I have set up an animation and want to animate a View, so when the user touch moves the view follows on the y axis.
I have tried to set this up with Animated.event but it doesn't work. Can somebody please explain how Animated.event works in a panResponder?
This is what I have:
const onPanMove = (evt: GestureResponderEvent, state: PanResponderGestureState) => {
Animated.event([null, {dy: new Animated.Value(state.dy)}], {useNativeDriver: true});
};
But nothing happens. I also googled a lot, but didn't find any good documentation on this.
When I replace the event with timing it works, but it's super laggy since it always waits for the touchmove to pause.
Animated.timing(
this.state.position,
{
toValue: startY - state.dy,
duration: 0,
//easing: this.props.easing,
useNativeDriver: true
}
).start();

React Native: Animation not working properly on android

I have been trying to fix issue for the past 2 days, it works fine on iOS
constructor(){
super();
this.animation = new Animated.Value(0)
this.state = {
expanded: false,
};
}
toggle(){
let initialValue = this.state.expanded? 1 : 0 ,
finalValue = this.state.expanded? 0 : 1
this.setState({
expanded: !this.state.expanded,
});
this.animation.setValue(initialValue)
Animated.timing(
this.animation,
{
toValue: finalValue,
}
).start();
}
render(){
return(
<Animated.View style={{{flex: 1, marginTop: 28, paddingLeft: 25, transform: [{ scale: this.animation }, {perspective: 1000 }]}}}>
....
</Animated.View>
);
}
This component is child , used in parent like this: <FeedBack ref={ref => this.feedback = ref}/> without any conditions to check to show or not (because animation scale is set to Zero in the constructor, so no need)
the toggle() method is being called from parent when a button pressed.
Now this works fine on iOS , when component loads, the view is not there until a button is pressed (then scaled). but on android when the component loads, the view already there. When I press the button, the animated view disappears and re-appears (with animation scaling) and subsequent toggles work fine. The problem in android is that even though initialValue of the scale is Zero, the view is still there when it first loads.
This has been an issue with react-native on the android side for a while now (sigh). It seems that setting the value to 0 strips it of its characteristics, basically deeming it as null and then reverting to using its actual value once it had animated to > 0
A work around is to set animation like so
this.animation = new Animated.Value(0.01);
You can follow the issue here

How to render absoulute element inside ListView or FlatList?

Here's a fun one i've been poking at for while:
I have a FlatList (same issue with ListView) and I want to render an element INSIDE the internal scrolling container with the following characteristics:
Absolutely Positioned (thus having no effect on position of list elements)
Position XX distance from top (translateY or top)
zIndex (above list elements)
The use case is i'm rendering a day view calendar grid with a horizontal bar at the current time position fixed at X distance from the beginning of the internal scrollview so it appears as the user scrolls pass that position.
So far i've tried wrapping wrapping FlatList/ListView with another ScrollView... also tried rendering this element as the header element which only works while the header/footer are visible (trashed when out of view).
Any and all ideas welcomed. :)
Thanks
Screenshot Below (red bar is what i'm trying to render):
Here's a working demo of what it sounds like you're trying to achieve: https://sketch.expo.io/BkreW1che. You can click "preview" to see it in your browser.
And here's the main code you need to measure the height of the ListView and place the indicator on top of it (visit the link above to see the full source):
handleLayout(event) {
const { y, height } = event.nativeEvent.layout;
// Now we know how tall the ListView is; let's put the indicator in the middle.
this.setState({ indicatorOffset: y + (height / 2) });
}
renderIndicator() {
const { indicatorOffset } = this.state;
// Once we know how tall the ListView is, put the indicator on top.
return indicatorOffset ? (
<View style={[{ position: 'absolute', left: 0, right: 0, top: indicatorOffset }]} />
) : null;
}
render() {
return (
<View style={styles.container}>
<ListView
onLayout={(event) => this.handleLayout(event)}
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
{this.renderIndicator()}
</View>
);
}
Edit: I now understand that you want the indicator to scroll along with the list. That's a simple change from above, just add an onScroll listener to the ListView: https://sketch.expo.io/HkEjDy92e
handleScroll(event) {
const { y } = event.nativeEvent.contentOffset;
// Keep the indicator at the same position in the list using this offset.
this.setState({ scrollOffset: y });
},
With this change, the indicator actually seems to lag behind a bit because of the delay in the onScroll callback.
If you want better performance, you might consider rendering the indicator as part of your renderRow method instead. For example, if you know the indicator should appear at 10:30 am, then you would render it right in the middle of your 10am row.

How to make draggable remember it's location in React Native

I'm learning about the animation and panresponder apis in react native. Following Animated Drag and Drop with React Native and the source
I want to extend the example so that you can drag the circle without having to reset it. Currently the code animates the circle back to the center of the screen and relies on the panresponders dX/dY values.
my best guess is I have to change something in onPanResponderMove
onPanResponderMove: Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
}]),
What do I need to change in the source so if I comment out the onPanResponderRelease logic the circle properly drags around the screen?
getTranslateTransform()?
The logic in onPanResponderRelease is making it so that if the draggable is not in the dropzone when released, it is reset back to 0,0 (these are the lines causing it).
Removing that logic alone isn't all you should do though. You should set the offset and reset the value of this.state.pan in onPanResponderRelease. To do this you need to track the value.
In componentDidMount, add this:
this.currentPanValue = {x: 0, y: 0};
this.panListener = this.state.pan.addListener((value) => this.currentPanValue = value);
Then, in componentWillUnmount:
this.state.pan.removeListener(this.panListener);
Now that you have the current value, you just add this to the PanResponder:
onPanResponderRelease: (e, gestureState) => {
this.state.pan.setOffset({x: this.currentPanValue.x, y: this.currentPanValue.y});
this.state.pan.setValue({x: 0, y: 0});
},
It seems more complicated than it actually is. Basically you are just setting the offset from 0/0, then setting the value to 0/0 so that when you start moving it again after having released it before, it doesn't jump back to 0/0 before jumping back to where your finger is. It also makes sure you have its current position...which you will probably need at some point anyways.
Another way would be to track current offset and keep adding it to the pan. So declare this in the constructor currentPanValue : {x: 0, y: 0},
And edit the onPanResponderRelease to the following :
onPanResponderRelease : (e, gesture) => {
this.state.currentPanValue.x += this.state.pan.x._value;
this.state.currentPanValue.y += this.state.pan.y._value;
this.state.pan.setOffset({x: this.state.currentPanValue.x, y: this.state.currentPanValue.y});
this.state.pan.setValue({x: 0, y: 0});
}
Incase if you want the final coordinates, you should get it with this.state.currentPanValue
Actually the only thing you need to change is the onPanResponderRelease function. What you are currently doing is applying the delta of the gesture movement to the initial position of the component. What you want to do is applying it to the position at the end of the last gesture. So you somehow need to save the offset. AnimatedValueXY.setOffset to the rescue!
onPanResponderRelease : (e, gesture) => {
if(this.isDropZone(gesture)){
this.setState({
showDraggable : false
});
}else{
this.state.pan.setOffset({
x: this.state.pan.x._offset + e.dx,
y: this.state.pan.y._offset + e.dy,
})
this.state.pan.setValue({x: 0, y: 0})
}
I'm using the internal _offset variable here because I could find a way to access it via the API. You can off course also just keep track of the offset manually. I didn't test this code yet, but you get the idea.