Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding react-native-reanimated execution

I'm new to react-native-reanimated and trying to wrap my head around how it works. The code below renders a box in the middle of the screen. On the initial render, the box translates to the right for 4 seconds, after which its position is reset to the middle of the screen.

... imports omitted for brevity

export default class App extends React.Component {
  state = {
    clock: new Clock(),
    translation: new Value(0),
  };

  onPress = () => {
    startClock(this.state.clock);
  };

  getTranslation = (clock, translation) => {
    const state = {
      finished: new Value(0),
      position: translation,
      time: new Value(0),
      frameTime: new Value(0),
    };
    const config = {
      duration: 4000,
      toValue: new Value(300),
      easing: Easing.inOut(Easing.ease),
    };
    return block([
      cond(clockRunning(clock), 0, [
        set(state.finished, 0),
        set(state.position, 0),
        set(state.time, 0),
        set(state.frameTime, 0),
        startClock(clock),
      ]),
      timing(clock, state, config),
      cond(state.finished, set(state.position, 0)),
      state.position,
    ]);
  };

  render() {
    const translation = this.getTranslation(
      this.state.clock,
      this.state.translation
    );
    return (
      <View style={styles.container}>
        <TouchableOpacity onPress={this.onPress}>
          <Animated.View
            style={{
              transform: [
                {
                  translateX: translation,
                },
              ],
              width: 100,
              height: 100,
              backgroundColor: "tomato",
            }}
          />
        </TouchableOpacity>
      </View>
    );
  }
}

My questions are:

1) Why does the box only translate to the right on the initial render? What prevents the animation from repeating?

2) The onPress handler doesn't restart the animation. Why?

like image 692
Bibs Avatar asked Dec 08 '18 21:12

Bibs


1 Answers

I too am learning react native reanimated, but I will try to answer your questions. If anybody would like to edit the answer, please free to edit.

  1. Why does the box only translate to the right on the initial render? What prevents the animation from repeating?

When the component mounts, the translateX value is 0, which will be at the centre of the screen (because as you have told, the styles.container might have styles that centres its child component).

And since you are asking for the translation from the getTranslation function inside the render method, when the component renders for the first time, the getTranslation function is called and a block of nodes are evaluated:

  1. The first node from the block list executes, i.e., the cond block. It checks if the clock is running.
    • If the clock is running, then do nothing.
    • Else, set the timing state and configuration. Please keep in mind that the state.position is set to 0 and also config.toValue is already set to 300. And start the clock.
  2. The timing function is given the state and its configurations.
    • It updates the position for a time frame. Here, since you have set the state.position to 0 and the config.toValue to 300, the state.position is updated to 1 (depends on the config.duration).
    • Also if the clock is running, it enqueues a callback to be evaluated with the next frame.
  3. Inside the 2nd last node of the block list, it checks whether the state's finished value is 1.
    • When the animation is completed, or better, when the value of state's position equals to the value of config's toValue, the timing function updates the value of finished to 1.
    • So, when the animation is completed, the state's position value is again set to 0. Meaning the position resets to its original position.
  4. On the last node, the state's position is returned to the render method to translation constant for the transform.

    Since the clock is started, and animation is not finished, the block of nodes runs again, and again. Meaning on the next frame, the state.position value will be 2, and later 3, and so on until the state.position equals to 300 (config.toValue). So the box will go from the center to the right side the screen. If, however you would have set the config.toValue to -300, the box will go from center to the left side of the screen.

    Finally, when the animation is finished, the 3rd node of the block equals to true, and the state.position is again to 0 (at the center of the screen).

    And since you have not stopped the clock (which can be done with stopClock(clock)), the 1st node to check clockRunning(clock) is always true. Also to repeat the animation, you will have to reset all the timing state and config after the animation is finished.

    So you have to change your block of nodes:

            return block([
                cond(
                    clockRunning(clock),
                    [
                        debug('clock is running', clock)
                    ],
                    [
                        debug('clock is NOT running', clock),
                        startClock(clock),
                    ]
                ),
                timing(clock, state, config),
                cond(
                    state.finished,
                    [
                        stopClock(clock),
                        set(state.finished, 0),
                        set(state.position, 0),
                        set(state.time, 0),
                        set(state.frameTime, 0),
                        startClock(clock),
                    ]
                ),
                state.position,
            ]);
    
  1. The onPress handler doesn't restart the animation. Why?

Because the clock was not stopped. Also because the state and config was not reset. So, to start the animation when pressed, this can be done in many different ways. I am going to show you how to in one of the ways, this way may be you will understand a bit more about react-native-reanimated and also react-native-gesture-handler to work with purely native animations, without crossing the bridge.

const getTranslation = ({clock, gestureState, translation}) => {
    const state = {
        finished: new Value(0),
        position: translation,
        time: new Value(0),
        frameTime: new Value(0),
    };
    const config = {
        duration: 2000,
        toValue: new Value(150),
        easing: Easing.inOut(Easing.ease),
    };
    return block([
        cond(
            clockRunning(clock),
            [
                debug('clock is running', clock)
            ],
            [
                debug('clock is NOT running', clock),
                set(state.finished, 0),
                set(state.position, 0),
                set(state.time, 0),
                set(state.frameTime, 0),
                startClock(clock),
            ]
        ),
        timing(clock, state, config),
        cond(
            state.finished,
            stopClock(clock)
        ),
        state.position
    ])
};

export default class App extends React.Component {

    gestureState = new Value(-1)

    clock = new Clock()

    translation = cond(
        eq(this.gestureState, State.ACTIVE), // when you start drag, the state will be ACTIVE
        [
            debug('active', this.gestureState, State.ACTIVE),
            getTranslation({clock: this.clock, translation: new Value(0)})
        ],
        [
            debug('not active', this.gestureState, State.ACTIVE)
        ],
    )

    onStateChange = event([
        {
            nativeEvent: {
                state: this.gestureState
            }
        }
    ])

    render() {

        return (
            <View style={styles.container}>
                <PanGestureHandler
                    onGestureChange={this.onStateChange}
                    onHandlerStateChange={this.onStateChange}>
                    <Animated.View style={[
                        styles.box,
                        {
                            transform: [{
                                translateX: this.translation
                            }]
                        }
                    ]}/>
                </PanGestureHandler>
            </View>
        )
    }
}
like image 148
Kakar Avatar answered Nov 02 '22 23:11

Kakar