I'm trying implement a 'drag down to dismiss' gesture in my React Native app using Animated.decay to take the velocity from the onPanResponderRelease handler and feed that into the decay.
The only this is I don't really understand what my velocity and deceleration should be. I'm having trouble understanding the meaning of these values. Any values I put in (whether it's the gesturestate.vy from onPanResponderRelease or static values I enter manually) don't result in a fluid animation and the view just disappears immediately.
I'm playing with values like
Animated.decay(this.props.dismissVal, {
velocity: 0.03,
deceleration: 0.997
});
And dismissVal is used like
top: this.props.dismissVal.interpolate({
inputRange: [0, 1],
outputRange: [0, _containerLayout.height]
})
I've looked a lot over the react-native-animated-tinder example, with no luck. I'm having trouble understanding what the value of velocity (and deceleration) should look like.
Do not forget start decay. Animated.decay({...}).start();
And the second mistake could be trying of change this.props. Everything what you pass to component as a prop, can be change only in parent component. In your child component it is final variable.
//constructor
this.state = { animatedValue: new Animated.Value() }
....
....
Animated.decay(this.state.animatedValue, {...}).start();
Related
I'd like to be able to change the toValue of an animation responding to props update. The official docs for React Native Animated API state that the spring method
animates a value according to an analytical spring model based on damped harmonic oscillation. Tracks velocity state to create fluid motions as the toValue updates, and can be chained together.
However, I haven't found anywhere how we can update toValue. Basically, my component looks like this:
const ProgressBar = ({ loadPercentage }) => {
const loadAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
animation.current = spring(loadAnim, {
toValue: loadPercentage,
}).start();
}, [loadAnim, loadPercentage]);
....
}
This doesn't work for all cases. In particular, if loadPercentage changes too often, the component takes up a huge amount of resources. This kinda makes sense, since I'm creating a new animation for each update. Instead, I'd like to simply modify toValue without starting a new animation or anything like that.
This seems pretty basic, but after 4 hours of trying stuff/googling, I give up. -.-
Just in case, I also tried using react-native-reanimated, but no luck there either.
Trying to add a listener to an Animated Diff Clamp in react native to allow the changing of a couple of non animated properties as well.
It initially performs correctly but throws an "illegal node" error when the screen scrolls beyond its max property.
"illegal node ID set as an input for Animated.DiffClamp"
Most articles on this are several years out of date and suggest that you can't add a listener to a diff clamp. However, it does work (now?) but only if declared in the constructor.
Not all situations allow it to be up there as it is not responsive to state changes when not inside render. Inside render it performs fine before reaching max and throwing the error above.
Render () {
const yHeader = Animated.diffClamp(
this.props._yScroll.interpolate({
inputRange: [0, 48],
outputRange: [0, -48],
extrapolateLeft: 'clamp',
}),
-48, 0)
yHeader.addListener(
Animated.event([{value: this.value.ySearch}], {useNativeDriver: false})
)
// this.value.search has been declared in the constructor as an Animated value and is waiting to receive the props. This is not the problem!
}
If still scrolling when the error is thrown, you can see that it continues performing correctly if outputting its current value in console.log despite an "illegal" error completely blocking the screen.
Is it possible to add a listener to a Diff Clamp?
If not, why does it work if the DiffClamp is in the constructor?
Is there a workaround here assuming the first question is no and I haven't ballsed something up somewhere?
This solves the problem but seems like a highly unnecessary workaround.
It appears we cannot add a listener "legally" to an Animated Diff Clamp when that Diff Clamp is declared in the Render. (Even though we can see it working in console log we get an error covering the screen)
The solution is to take the value of the Diff Clamp and interpolate that into a new variable.
//
const yHeader = Animated.diffClamp(
this.props._yScroll.interpolate({
inputRange: [0, 48],
outputRange: [0, -48],
extrapolateLeft: 'clamp',
}),
-48, 0)
const yHeader_legalised = yHeader.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
})
yHeader_legalised.addListener(
Animated.event([{value: this.value.ySearch}], {useNativeDriver: false})
)
PS. You will have to lose yHeader_legalised in context in the render in place of yHeader in order for the listener to have something to listen to.
I am facing a problem that I've tried to solve in lots of different ways, but I cannot get it to work. Please see this Expo application, I've created a dumb example that demonstrates my problem: https://snack.expo.io/HJB0sE4jS
To summarize, I want to build an app with a draggable component (The blue dot in the example), but while the user drags the component I also need to update the state of the app (the counter in the example). The problem is that whenever the state updates during dragging, the component resets to it's initial position. I want to allow the user to freely drag the component while state updates happen.
I was able to "solve" the issue by putting the PanResponder in a useRef, so it won't be reinitialized in case of a state update, but as you can see in the example, I want to use the state in the PanResponder. If I put it in a useRef I cannot use the state in the PanResponder because it will contain a stale value (it will always contain the initial value of the counter which is 0).
How do you handle these kind of situations in react native? I guess it is not too uncommon that someone wants to update the state during an animation, although I cannot find any documentation or examples on this.
What am I doing wrong?
Edit: I was investigating further and I can see that the problem is that I'm mapping the (dx,dy) values from the gesture parameter to the position, but the (dx,dy) values are reset to (0,0) when the state changes. I guess (dx,dy) initialized to (0,0) when PanResponder is created. Still don't know what to do to make this work...
A mutable ref that holds the latest counter state value, along with a ref to prevent re-initializing the PanResponder should solve the problem in the example:
const [counter] = useCounter();
// Update the counterValue ref whenever the counter state changes
const counterValue = useRef(counter);
useEffect(() => {
counterValue.current = counter;
}, [counter]);
const position = useRef(new Animated.ValueXY());
const panResponder = useRef(PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: Animated.event(
[null, { dx: position.current.x, dy: position.current.y }],
{ listener: () => console.log(counterValue.current) } // counterValue being a ref, will not go stale
),
onPanResponderRelease: () => {
Animated.spring(position.current, { toValue: { x: 0, y: 0 } }).start();
}
})
);
You can check the above suggestion here: https://snack.expo.io/rkMLgp4jB
I understand though that this is a rather simplified example, and may not work for your actual use-case. It would help if you could share some more details on the actual usage!
I have some state variables I want to animate for user micro-interaction. These variables are not properties of a view or style properties; they are actually properties of an SVG such as Circle radius (using react-native-svg). I have used window.requestAnimationFrame to animate these circles and it works buttery smooth but I would like to get features of React.Animated and Easing and maybe reduce some code if this is possible.
So I am struggling with getting Animated.timing(...).start() to update my state for each frame so the rendering will occur.
In an event handler I have something like:
let radiusValue = new Animated.Value(0);
this.setState({ holding: {
radius: radiusValue,
animator: Animated.timing(
radiusValue,
{
toValue: closest.radius*1.15,
duration: 1,
easing: Easing.bounce(3)
}
).start(() => console.log("done"))
}
So that sets up the animation. Now somewhere in my render() code I have:
<Animated.View>
<Svg><Circle cx={x} cy={y} radius={this.state.radius._value}</Svg>
</Animated.View>
Now because my radius Animated.Value is not part of Animated.View props I guess it doesnt generate tween frames. I do get the "done" message from within the Animated.timing(..).start(callback) but obviously since nothing is directly wired up to modifying my state I don't get calls to render for tweening. So how do I get the Animated.timing() to modify my state?
I've tried converting the Svg to an Animated control but this actually crashes the iOS Simulator to home screen (no red screen exception error).
let AnimatedSvg = Animated.createAnimatedControl(Svg);
...
<AnimatedSvg><Circle cx={x} cy={y} radius={this.state.radius._value}</AnimatedSvg>
Is there a callback from Animated.timing(...) that I could set to then call setState()?
The code above is very pseudo, trying to keep the question light.
We were asked to implement a screen for our app where there would be some data in the form of a list and sections for each data category. When a Section header is pressed, the section data should be expanded or collapsed.
At first, i tried with Listview, and i was changing the datasource each time a header was pressed, but tbh it did not feel like the correct way of doing it.
Creating a custom view, an animating the view's height works ok, but because of the volume of the data, which is big, the initial rendering is a bit slow e.g. there is a noticeable delay when navigating to the screen.
After upgrading to RN 44.3 i was wondering if i could use Sectionlist in a better way than listview.
Generally what's the best way of approaching a requirement like this?
Thanks!
You can animate the items in flatlist/SectionList. A smaple code will look like below (Animates on removing items). You can use same logic for section list as well.
onRemove = () => {
const { onRemove } = this.props;
if (onRemove) {
Animated.timing(this._animated, {
toValue: 0,
duration: ANIMATION_DURATION,
}).start(() => onRemove());
}
};
Refer this link for more details.