DEV Community

Cover image for Let's reanimate
Sidharth Juyal
Sidharth Juyal

Posted on • Originally published at whackylabs.com on

Let's reanimate

Drawing text and images is one thing but the real test of a UI framework is in how good is it with animating content.

And my test for animation is the classic MoveMe based on the Apple’s sample code.

The idea is to draw three boxes on the screen. When selected the box changes color and scales up and then can be moved around with the drag gesture and eventually restores back to original color and size when released.

Let’s build that sample using React Native’s Reanimated library.

Setup

I’m following the official docs but not using their template. So I’ve a basic project created with the blank template and installed the dependencies

npx create-expo-app playground --template blank
npx expo install react-native-reanimated
npx expo install react-native-gesture-handler

Enter fullscreen mode Exit fullscreen mode

Next, I added the plugin

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: ["react-native-reanimated/plugin"],
  };
};

Enter fullscreen mode Exit fullscreen mode

And then draw 3 squares on screen:

import { StatusBar } from "expo-status-bar";
import { StyleSheet, View } from "react-native";
import Animated from "react-native-reanimated";

function Square() {
  return <Animated.View style={styles.square}></Animated.View>;
}

export default function App() {
  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <Square />
      <Square />
      <Square />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "space-evenly",
  },
  square: {
    width: 100,
    height: 100,
    backgroundColor: "blue",
  },
});

Enter fullscreen mode Exit fullscreen mode

set up

Add gesture handler

To add support for gesture handlers we first need to wrap the content within the GestureHandlerRootView

<GestureHandlerRootView style={styles.container}>
  <Square />
  <Square />
  <Square />
</GestureHandlerRootView>

Enter fullscreen mode Exit fullscreen mode

And then wrap each Square within GestureDetector

function Square() {
  const gesture = Gesture.Pan();

  return (
    <GestureDetector gesture={gesture}>
      <Animated.View style={styles.square} />
    </GestureDetector>
  );
}

Enter fullscreen mode Exit fullscreen mode

Handle gesture events

To handle gesture we first need to create a SharedValue which is like State but for animation states. For example, to change the background color when selected we need to listen to onBegin and onFinalize events and update the style:

function Square() {
  const isPressed = useSharedValue(false);
  const animStyle = useAnimatedStyle(() => {
    return {
      backgroundColor: isPressed.value ? "red" : "blue",
    };
  });

  const gesture = Gesture.Pan()
    .onBegin(() => {
      isPressed.value = true;
    })
    .onFinalize(() => {
      isPressed.value = false;
    });

  return (
    <GestureDetector gesture={gesture}>
      <Animated.View style={[styles.square, animStyle]} />
    </GestureDetector>
  );
}

Enter fullscreen mode Exit fullscreen mode

Supporting drag is similar. We need to store start and current positions and then update the current position on onChange event. The onChange provides the delta change that we then need to add to the start position to calculate the final current position. And then, finally at the onFinalize event we can sync the start and current positions.

function Square() {
  const isPressed = useSharedValue(false);
  const startPos = useSharedValue({ x: 0, y: 0 });
  const pos = useSharedValue({ x: 0, y: 0 });
  const animStyle = useAnimatedStyle(() => {
    return {
      backgroundColor: isPressed.value ? "red" : "blue",
      transform: [
        { translateX: pos.value.x },
        { translateY: pos.value.y },
        { scale: withSpring(isPressed.value ? 1.2 : 1) },
      ],
    };
  });

  const gesture = Gesture.Pan()
    .onBegin(() => {
      isPressed.value = true;
    })
    .onChange((e) => {
      pos.value = {
        x: startPos.value.x + e.translationX,
        y: startPos.value.y + e.translationY,
      };
    })
    .onFinalize(() => {
      isPressed.value = false;
      startPos.value = {
        x: pos.value.x,
        y: pos.value.y,
      };
    });

  return (
    <GestureDetector gesture={gesture}>
      <Animated.View style={[styles.square, animStyle]} />
    </GestureDetector>
  );
}

Enter fullscreen mode Exit fullscreen mode

And there you have it

final

References

Top comments (0)