Reputation: 10451
I have a simple looping animation in my component like this:
runAnimation() {
console.log('run animation');
this.state.angle.setValue(0);
Animated.timing(this.state.angle, {
toValue: 360,
duration: 8000,
easing: Easing.linear
}).start(() => this.runAnimation());
}
...
<Animated.Image
style={[
styles.rotate,
{ transform: [
{ rotate: this.state.angle.interpolate({
inputRange: [0, 360],
outputRange: ['0deg', '360deg']
})},
]}
]}
source={require('./spinning_ball.png')}
/>
How would I stop this animation? For example, when navigating away to another screen or after a user clicks on a button.
I tried using this.state.angle.stopAnimation() but noticed run animation still being printed in the console. Is there a different stop method I should be calling to prevent the start callback from being executed?
Upvotes: 12
Views: 21636
Reputation: 1490
I like to encapsulate my animations in hooks, it makes for a much cleaner, easier, and more reusable pattern than class components. Here's how I do a looping animation with easy start/stop control in typescript:
export const useBounceRotateAnimation = (running: boolean = true, rate: number = 300) => {
//Example of making an infinite looping animation and controlling it with a boolean
//Note that this assumes the "rate" is constant -- if you wanted to change the rate value after creation the implementation would have to change a bit
//Only create the animated value once
const val = useRef(new Animated.Value(0))
//Store a reference to the animation since we're starting-stopping an infinite loop instead of starting new animations and we aren't changing any animation values after creation. We only want to create the animation object once.
const anim = useRef(
Animated.loop(
Animated.timing(val.current, {
toValue: 1,
duration: rate,
easing: Easing.linear,
useNativeDriver: true,
isInteraction: false,
})
)
).current
//Interpolate the value(s) to whatever is appropriate for your case
const interpolatedY = val.current.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 6, 0],
})
const interpolatedRotate = val.current.interpolate({
inputRange: [0, 0.25, 0.5, 1],
outputRange: ["0deg", "-3deg", "3deg", "0deg"],
})
//Start and stop the animation based on the value of the boolean prop
useEffect(() => {
if (running) {
anim.start()
} else {
//When stopping reset the value to 0 so animated item doesn't stop in a random position
anim.stop()
val.current.setValue(0)
}
//Return a function from useEffect to stop the animation on unmount
return () => anim.stop()
//This useEffect should rerun if "running" or "anim" changes (but anim won't change since its a ref we never modify)
}, [running, anim])
//Return the animated values. Use "as const" const assertion to narrow the output type to exactly the two values being returned.
return [interpolatedY, interpolatedRotate] as const
}
I use this actual hook implementation to make a character image "walk" in my game. Now in my component it's very easy to use:
const MyComponent = () => {
const [isRunning, setIsRunning] = useState(true)
const [translation, rotation] = useBounceRotateAnimation(isRunning)
return (
<Animated.View
style={{
transform: [{ translateY: translation}, { rotate: rotation}],
}}
>
....rest of your component here, just setIsRunning(false) to stop animation whenever you need
</Animated.View>
)
}
As you can see this pattern is very clean and reusable. The animated component doesn't need to know anything about the animation, it just consumes the animated values and tells the animation when to run.
Upvotes: 11
Reputation: 1321
The simplest way to stop looping animation by calling setValue()
method and assign a value. like:
const [opacity, setOpacity] = useState(new Animated.value(0));
// declare your looping animated function
// Any time to stop the looping animated function just call setValue()
// on your stop animation event or function using opcacity reference. like
opacity.setValue(0)
For more details https://reactnative.dev/docs/animatedvaluexy#setvalue
Upvotes: 1
Reputation: 46
You can stop the loop using:
componentWillUnmount() {
Animated.timing(this.state.angle).stop();
}
Upvotes: 1
Reputation: 6689
Based on my comment in Nguyên Hoàng's answer. Here is another way to stop the looping animation if you call this.state.angle.stopAnimation()
:
runAnimation() {
this.state.angle.setValue(0);
Animated.timing(this.state.angle, {
toValue: 360,
duration: 8000,
easing: Easing.linear
}).start((o) => {
if(o.finished) {
this.runAnimation();
}
});
}
Upvotes: 21
Reputation: 41
Set a global variable
let dataloaded = false;
To stop animation override the variable (dataloaded) whenever you want
componentWillUnmount() {
dataloaded = true;
}
onPress={() => {
dataloaded = true;
}}
final code will look like
runAnimation() {
console.log('run animation');
this.state.angle.setValue(0);
Animated.timing(this.state.angle, {
toValue: 360,
duration: 8000,
easing: Easing.linear
})
.start(() => {
if ( ! dataloaded) {
this.runAnimation();
}
})
}
Upvotes: 0
Reputation: 1053
You can create a variable stopAnimation
to stop Animation whenever you want by only when stopAnimation === false
then callback runAnimation
function. Like example:
this.state = { stopAnimation: false }
runAnimation() {
this.state.spinValue.setValue(0);
Animated.timing(
this.state.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear
}
).start( () => {
if(this.state.stopAnimation === false) {
this.runAnimation();
}
});
}
So you just need to create a button which call function this.state = { stopAnimation: true }
to stop Animation.
Example here: https://rnplay.org/apps/Lpmh8A.
Upvotes: 5