Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop a looping animation in React Native?

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?

like image 289
Michael Cheng Avatar asked Feb 10 '17 01:02

Michael Cheng


People also ask

How do I stop loop animation in React Native?

If an animation is in process of being animated and, for any particular reason, you need to stop it, you can call stopAnimation . The stopAnimation call also takes a callback with the value that the animation was stopped on.

How do you stop an animation from looping in after effects?

Right-Click and Select Freeze Frame. Or from menu "Layer->Time->FreezeFrame". your layer will freeze at this point.. you can extend or trim this layer as long as you want.

How do you check if animation is finished React Native?

start() ​ Animations are started by calling start() on your animation. start() takes a completion callback that will be called when the animation is done or when the animation is done because stop() was called on it before it could finish.

What is animation looping?

Looping an animation causes it to repeat. You can loop animations in Advanced mode. Each element's animation can be looped separately, or you can loop a more complex animation involving multiple elements.


5 Answers

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();
    }
  });
}
like image 194
max23_ Avatar answered Oct 26 '22 23:10

max23_


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.

like image 42
imagio Avatar answered Oct 26 '22 23:10

imagio


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.

like image 23
Nguyên Hoàng Avatar answered Oct 27 '22 00:10

Nguyên Hoàng


You can stop the loop using:

componentWillUnmount() {
   Animated.timing(this.state.angle).stop();
}
like image 41
Quizzy Avatar answered Oct 26 '22 23:10

Quizzy


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

like image 23
Ikram - Ud - Daula Avatar answered Oct 26 '22 23:10

Ikram - Ud - Daula