React-native-reanimated: Animate Value when prop changes

Created on 6 May 2020  路  5Comments  路  Source: software-mansion/react-native-reanimated

How can I animate the Y Value to 100 when the updated prop changes from false to true? Thanks!

For instance, say the user panned the Animated.View so that the Y Value is now 300 and then pressed a button. When they pressed that button, I want to animate the Y Value to 100. Afterward the user can pan the Animated.View Y Value to wherever they want.

import React, { useRef, useState } from 'react';
import { StyleSheet } from 'react-native';
import { PanGestureHandler, PinchGestureHandler, RotationGestureHandler, State } from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';

const { 
  add,
  block,
  call,
  concat,
  cond,
  debug,
  event,
  eq,
  greaterThan,
  multiply,
  onChange,
  set,
  timing,
  useCode,
  Value,
} = Animated;

const styles = StyleSheet.create({
  area: {
    position: 'absolute',
    top: 0,
    zIndex: 999,
  },
  box: {
    backgroundColor: 'lightblue',
    height: 200,
    position: 'absolute',
    top: 0,
    width: 200,
  },
});

const Move = ({ updated }) => {
  const rotationRef = useRef();
  const panRef = useRef();
  const pinchRef = useRef();

  const offsetX = new Value(0);
  const offsetY = new Value(0);
  const offsetR = new Value(0);
  const offsetZ = new Value(1);

  const [X] = useState(new Value(0));
  const [Y] = useState(new Value(0));
  const [R] = useState(new Value(0));
  const [Z] = useState(new Value(1));

  const onPan = event([
    {
      nativeEvent: ({ translationX: x, translationY: y, state }) => block([
        set(X, add(x, offsetX)),
        set(Y, add(y, offsetY)),
        cond(
          eq(state, State.END),
          [
            set(offsetX, add(offsetX, x)),
            set(offsetY, add(offsetY, y)),
          ],
        ),
      ]),
    },
  ]);

  const onRotate = event([
    {
      nativeEvent: ({ rotation: r, state }) => block([
        set(R, add(r, offsetR)),
        cond(
          eq(state, State.END), 
          [set(offsetR, add(offsetR, r))]
        ),
      ]),
    },
  ]);

  const onZoom = event([
    {
      nativeEvent: ({ scale: z, state }) => block([
        cond(eq(state, State.ACTIVE), set(Z, multiply(z, offsetZ))),
        cond(eq(state, State.END), [set(offsetZ, multiply(offsetZ, z))]),
      ]),
    },
  ]);

  return (
    <>
      <PanGestureHandler
        minDist={10}
        ref={panRef}
        onGestureEvent={onPan}
        onHandlerStateChange={onPan}
        simultaneousHandlers={[rotationRef, pinchRef]}
      >
        <Animated.View style={styles.area}>
          <PinchGestureHandler
            onGestureEvent={onZoom}
            onHandlerStateChange={onZoom}
            ref={pinchRef}
            simultaneousHandlers={[rotationRef, panRef]}
          >
            <Animated.View>
              <RotationGestureHandler
                onGestureEvent={onRotate}
                onHandlerStateChange={onRotate}
                ref={rotationRef}
                simultaneousHandlers={[pinchRef, panRef]}
              >
                <Animated.View
                  style={[
                    styles.box,
                    {
                      transform: [
                        { translateX: X },
                        { translateY: Y },
                        { rotate: concat(R, 'rad') },
                        { scale: Z },
                      ],
                    }
                ]}
                />
              </RotationGestureHandler>
            </Animated.View>
          </PinchGestureHandler>
        </Animated.View>
      </PanGestureHandler>
    </>
  );
};

export default Move;

鉂換uestion

All 5 comments

Hi @noobvim,

try creating some Value and using it as a flag:

const shouldAnimate = new Value(0);

const onClick = () => shouldAnimate.setValue(1);

// somewhere in your animation code
cond(shouldAnimate, 
    set(shouldAnimate, 0)
    set(X, 100)
);

Does it answer your question?

Kind of...
The code below does not work. However, if I change const shouldAnimate = new Value(0) to const shouldAnimate = useState(new Value(0)), then it does work. Do you know why that would be?

Also, when switching shouldAnimate between 0 and 1, how can I transition smoothly between set(Y, add(y, offsetY)) and set(Y, 100)? Thanks again! I really appreciate your help.

const shouldAnimate = new Value(0);

useEffect(() => {
  shouldAnimate.setValue(updated ? 1 : 0);
}, [updated]);

const onPan = event([
  {
    nativeEvent: ({ translationX: x, translationY: y, state }) => block([
      cond(
        shouldAnimate,
        [
          set(Y, add(y, offsetY)),
        ],
        [
          set(Y, 100),
        ],
      ),
      cond(
        eq(state, State.END),
        [
          set(offsetX, add(offsetX, x)),
          set(offsetY, add(offsetY, y)),
        ],
      ),
    ]),
  },
]);

@noobvim It happens because in FunctionComponent, using direct assignment:
const shouldAnimate = new Value(0)
you recreate shouldAnimated each render. This does not happen when using useState - new Value(0) is only initial value there.
In other words, shouldAnimated_fromRender1 === shouldAnimated_fromRender2 equals true for useState, but false for direct assignment.

Thanks @DrRefactor that was helpful.

@noobvim, adding to the @DrRefactor answer, you can use useRef/useMemo to preserve old reference between rerenders. You can look at #786 to check out how you can do it.

Was this page helpful?
0 / 5 - 0 ratings