Using react-native-reanimated, I'm trying to repeat infinitely an animation which is also a repeated animation, with a delay.
With my code, the delay and the nested repeat animation are triggered but not the inifinite one.
Any ideas ?
useEffect(() => {
progress.value = withRepeat(
withDelay(2000, withRepeat(withTiming(0, { duration: 500 }), 6, true)),
-1,
true
);
}, []);
Like #Abe pointed out in his answer, reverse property of withRepeat is not supported when wrapped with other animation modifiers.
But, you can do this without setInterval - making use of withSequence to simulate the `reverse animation
// starting delay of 2000ms
// play animation 6 times
// repeat
progress.value =
withRepeat(
withDelay(2000, withRepeat(
withSequence(
// split duration of 500ms to 250ms
withTiming(goToValue, {duration: 250, easing: Easing.inOut(Easing.ease)}),
withTiming(initialValue, {duration: 250, easing: Easing.inOut(Easing.ease)}),
)
, 6))
, -1)
You've set the outer withRepeat to 1, so it should only repeat once, is that intended? Use a negative number to repeat indefinitely.
The docs for withRepeat say that withRepeat with the third argument (reverse) set to true doesn't work properly for the withDelay and withSequence animations, that could also be causing an issue. You might want to try reversing the animations manually in a withSequence call and repeating that.
No solution found with Reanimated, but as suggested by #Abe,a simple setInterval does the trick
useEffect(() => {
progress.value = withRepeat(withTiming(0, { duration: 400 }), 6, true);
const interval = setInterval(() => {
progress.value = withRepeat(withTiming(0, { duration: 400 }), 6, true);
}, 6000);
return () => clearInterval(interval);
}, []);
You can achieve that without setInterval, put withDelay on each animation.
withRepeat(
withSequence(
withDelay(
2000,
withTiming(0, {
duration: 300,
easing: Easing.inOut(Easing.ease),
}),
),
withDelay(
2000,
withTiming(1, {
duration: 300,
easing: Easing.inOut(Easing.ease),
}),
),
),
-1,
);
You can achieve this by using Animated.sequence
This code basically works by re-run the function when the animation done
function fadeAnimation() {
Animated.sequence([
Animated.timing(progress.value, {
toValue: 0,
duration: 500,
delay: 1000,
useNativeDriver: true,
}),
Animated.timing(progress.value, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
]).start(() => {
fadeAnimation();
});
}
useEffect(() => {
fadeAnimation()
}, []);
I've started to play around with react-native-reanimated v2.
const fontSize = useSharedValue(25);
const config = {
duration: 500,
easing: Easing.bezier(0.5, 0.01, 0, 1),
};
const fontStyle = useAnimatedStyle(() => {
return {
fontSize: withTiming(fontSize.value, config, (isFinished) => {
if (isFinished) {
console.log('isFinished');
}
}),
};
});
return (
<Button
title="toggle"
onPress={() => {
fontSize.value = 1;
}}
/>
)
The above code block initially sets the font size to 25, and on press starts an animation where it gradually reduces it to 1.
When the animation is finished, I want to change the text.
However, the callback with isFinished is called instantly, before I press the button. The moment the app renders.
The code block is pretty much from their docs, so I'm unsure what I'm doing wrong.
Is it possible to make sure the callback is only called after the animation is finished? Not on render?
UPDATE
A hack I've put together is setting a state const [buttonPressed, setButtonPressed] = useState(false); and checking for this along with isFinished, and setting this to true when the button has been pressed.
But this is a horrible workaround.
I have a fade animation in my componentDidUpdate function :
if (prevState.fadeStarting !== this.state.fadeStarting){
const fadestart = new Animated.Value(1);
this.setState(
{fadeStarting: false,animFade:fadestart},
() => {
Animated.timing(
this.state.animFade, {
toValue: 0.0,
duration: 100000000,
}
).start(this.endFade())
}
)
}
I assumed that the endFade method would only be called after the duration expired, but the endFade method is called instantly. Is there any reason that this animation would end early?
Could you mean start(this.endFade) instead?
You mean to pass the callback function, not its result (which is evaluated immediately).
I am struggling with React Native Animations here. The outcome is simple, I have an Animated.Image which I want to spin.
All good, until I want to loop thru the animation for n times and do something when it stops.
I have the following code .
Animated.loop(
Animated.timing(this.state.spin, {
toValue: 360,
duration: 1000,
easing: Easing.linear,
useNativeDriver: true,
}), {
iterations: 3
}
).start(() => {
console.log('done');
});
It spins 3 times as per loop iteration but no callback was fired when animation ends.
Here the Expo which replicates this: https://snack.expo.io/S1PjnfB9-
Try the code below
Animated.loop(
Animated.timing(this.state.spin, {
toValue: 360,
duration: 1000,
easing: Easing.linear,
useNativeDriver: true,
}), {
iterations: 3
}
).start(event => {
if (event.finished) {
console.log('finished');
}
});
I just added a check on the event response.
Hope it helps.
Found the answer.
It seems that if you take out useNativeDriver it works as it should be, and the callback is called.
This is weird...
I am trying to figure out when the Animated.spring event has completed so I can update some state.
I have tried this, but finish throws an undefined error here;
Animated.spring(
this.state.pan,
{ toValue: { x: 0, y: -500 } }
).start().finish(
this.setState({
carousel: false, hasNotification: false
})
);
Any other methods for dealing with this?
Thanks
According to the documentation
start takes a completion callback that will be called when the animation is done. If the animation is done because it finished running normally, the completion callback will be invoked with {finished: true}, but if the animation is done because stop was called on it before it could finish (e.g. because it was interrupted by a gesture or another animation), then it will receive {finished: false}.
So I think this should work:
Animated.spring(this.state.pan, { toValue: { x: 0, y: -500 } }).start(()=>{
this.setState({
carousel: false, hasNotification: false
})
});
You can pass an optional callback:
Animated.spring(
this.state.pan,
{ toValue: { x: 0, y: -500 } }
).start(this.stopAnimation.bind(this))
stopAnimation()
{
this.setState({
carousel: false, hasNotification: false
})
}