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.
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
Next, I added the plugin
module.exports = function (api) { api.cache(true); return { presets: ["babel-preset-expo"], plugins: ["react-native-reanimated/plugin"], }; };
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", }, });
To add support for gesture handlers we first need to wrap the content within the GestureHandlerRootView
<GestureHandlerRootView style={styles.container}> <Square /> <Square /> <Square /> </GestureHandlerRootView>
And then wrap each Square within GestureDetector
function Square() { const gesture = Gesture.Pan(); return ( <GestureDetector gesture={gesture}> <Animated.View style={styles.square} /> </GestureDetector> ); }
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> ); }
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> ); }
And there you have it
The above is the detailed content of Lets reanimate. For more information, please follow other related articles on the PHP Chinese website!