React Native动画系统 #

概述 #

动画是提升用户体验的重要手段。React Native 提供了多种动画解决方案,本章节介绍 Animated API 和 Reanimated 库。

Animated API #

基本动画 #

tsx
import React, {useRef, useEffect} from 'react';
import {Animated, View, StyleSheet, Button} from 'react-native';

const FadeInExample = () => {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  const fadeIn = () => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  };

  const fadeOut = () => {
    Animated.timing(fadeAnim, {
      toValue: 0,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  };

  return (
    <View style={styles.container}>
      <Animated.View
        style={[
          styles.box,
          {
            opacity: fadeAnim,
          },
        ]}
      />
      <View style={styles.buttons}>
        <Button title="Fade In" onPress={fadeIn} />
        <Button title="Fade Out" onPress={fadeOut} />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: '#007AFF',
    marginBottom: 20,
  },
  buttons: {
    flexDirection: 'row',
    gap: 16,
  },
});

export default FadeInExample;

动画类型 #

timing #

时间线性动画:

tsx
Animated.timing(value, {
  toValue: 1,
  duration: 1000,
  easing: Easing.ease,
  useNativeDriver: true,
}).start();

spring #

弹簧动画:

tsx
Animated.spring(value, {
  toValue: 1,
  friction: 7,
  tension: 40,
  useNativeDriver: true,
}).start();

decay #

衰减动画:

tsx
Animated.decay(value, {
  velocity: 0.5,
  deceleration: 0.997,
  useNativeDriver: true,
}).start();

插值 #

tsx
const rotateAnim = useRef(new Animated.Value(0)).current;

const spin = rotateAnim.interpolate({
  inputRange: [0, 1],
  outputRange: ['0deg', '360deg'],
});

const colorAnim = fadeAnim.interpolate({
  inputRange: [0, 1],
  outputRange: ['rgb(255, 0, 0)', 'rgb(0, 255, 0)'],
});

const scaleAnim = fadeAnim.interpolate({
  inputRange: [0, 0.5, 1],
  outputRange: [1, 1.5, 1],
});

// 使用
<Animated.View
  style={{
    transform: [{rotate: spin}, {scale: scaleAnim}],
    backgroundColor: colorAnim,
  }}
/>

组合动画 #

parallel #

并行执行:

tsx
Animated.parallel([
  Animated.timing(fadeAnim, {
    toValue: 1,
    duration: 1000,
    useNativeDriver: true,
  }),
  Animated.spring(scaleAnim, {
    toValue: 1.5,
    friction: 3,
    useNativeDriver: true,
  }),
]).start();

sequence #

顺序执行:

tsx
Animated.sequence([
  Animated.timing(fadeAnim, {
    toValue: 1,
    duration: 500,
    useNativeDriver: true,
  }),
  Animated.spring(scaleAnim, {
    toValue: 1.5,
    friction: 3,
    useNativeDriver: true,
  }),
]).start();

stagger #

交错执行:

tsx
Animated.stagger(200, [
  Animated.timing(anim1, {toValue: 1, useNativeDriver: true}),
  Animated.timing(anim2, {toValue: 1, useNativeDriver: true}),
  Animated.timing(anim3, {toValue: 1, useNativeDriver: true}),
]).start();

循环动画 #

tsx
const startRotation = () => {
  Animated.loop(
    Animated.timing(rotateAnim, {
      toValue: 1,
      duration: 2000,
      useNativeDriver: true,
    }),
  ).start();
};

const startPulse = () => {
  Animated.loop(
    Animated.sequence([
      Animated.timing(scaleAnim, {
        toValue: 1.2,
        duration: 500,
        useNativeDriver: true,
      }),
      Animated.timing(scaleAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true,
      }),
    ]),
  ).start();
};

Reanimated #

Reanimated 是更强大的动画库,支持在 UI 线程运行动画。

安装 #

bash
npm install react-native-reanimated
cd ios && pod install

配置 #

babel.config.js 中添加:

javascript
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: ['react-native-reanimated/plugin'],
};

基本使用 #

tsx
import React from 'react';
import {View, StyleSheet, Button} from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withTiming,
} from 'react-native-reanimated';

const ReanimatedExample = () => {
  const offset = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{translateX: offset.value}],
    };
  });

  const move = () => {
    offset.value = withSpring(offset.value === 0 ? 200 : 0);
  };

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.box, animatedStyle]} />
      <Button title="Move" onPress={move} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: '#007AFF',
    marginBottom: 20,
  },
});

export default ReanimatedExample;

动画函数 #

withTiming #

tsx
offset.value = withTiming(100, {
  duration: 500,
  easing: Easing.ease,
});

withSpring #

tsx
offset.value = withSpring(100, {
  damping: 10,
  stiffness: 100,
  mass: 1,
});

withRepeat #

tsx
offset.value = withRepeat(
  withTiming(100, {duration: 500}),
  3, // 重复次数,-1 表示无限
  true, // 是否反向
);

withSequence #

tsx
offset.value = withSequence(
  withTiming(50, {duration: 200}),
  withSpring(100),
  withTiming(0, {duration: 300}),
);

手势动画 #

结合 react-native-gesture-handler:

bash
npm install react-native-gesture-handler
tsx
import React from 'react';
import {View, StyleSheet} from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';

const DragExample = () => {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const context = useSharedValue({x: 0, y: 0});

  const gesture = Gesture.Pan()
    .onStart(() => {
      context.value = {x: translateX.value, y: translateY.value};
    })
    .onUpdate(event => {
      translateX.value = context.value.x + event.translationX;
      translateY.value = context.value.y + event.translationY;
    })
    .onEnd(() => {
      translateX.value = withSpring(0);
      translateY.value = withSpring(0);
    });

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        {translateX: translateX.value},
        {translateY: translateY.value},
      ],
    };
  });

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

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: '#007AFF',
    borderRadius: 8,
  },
});

export default DragExample;

缩放手势 #

tsx
const PinchExample = () => {
  const scale = useSharedValue(1);
  const savedScale = useSharedValue(1);

  const gesture = Gesture.Pinch()
    .onUpdate(e => {
      scale.value = savedScale.value * e.scale;
    })
    .onEnd(() => {
      savedScale.value = scale.value;
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{scale: scale.value}],
  }));

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

旋转手势 #

tsx
const RotationExample = () => {
  const rotation = useSharedValue(0);
  const savedRotation = useSharedValue(0);

  const gesture = Gesture.Rotation()
    .onUpdate(e => {
      rotation.value = savedRotation.value + e.rotation;
    })
    .onEnd(() => {
      savedRotation.value = rotation.value;
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{rotateZ: `${rotation.value}rad`}],
  }));

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

组合手势 #

tsx
const CombinedGestureExample = () => {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const scale = useSharedValue(1);
  const savedTranslate = useSharedValue({x: 0, y: 0});
  const savedScale = useSharedValue(1);

  const panGesture = Gesture.Pan()
    .onStart(() => {
      savedTranslate.value = {x: translateX.value, y: translateY.value};
    })
    .onUpdate(e => {
      translateX.value = savedTranslate.value.x + e.translationX;
      translateY.value = savedTranslate.value.y + e.translationY;
    });

  const pinchGesture = Gesture.Pinch()
    .onUpdate(e => {
      scale.value = savedScale.value * e.scale;
    })
    .onEnd(() => {
      savedScale.value = scale.value;
    });

  const composedGesture = Gesture.Simultaneous(panGesture, pinchGesture);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      {translateX: translateX.value},
      {translateY: translateY.value},
      {scale: scale.value},
    ],
  }));

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

LayoutAnimation #

简单的布局动画:

tsx
import {LayoutAnimation, UIManager, Platform, Button, View} from 'react-native';

if (
  Platform.OS === 'android' &&
  UIManager.setLayoutAnimationEnabledExperimental
) {
  UIManager.setLayoutAnimationEnabledExperimental(true);
}

const LayoutAnimationExample = () => {
  const [expanded, setExpanded] = useState(false);

  const toggle = () => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
    setExpanded(!expanded);
  };

  return (
    <View style={styles.container}>
      <Button title="Toggle" onPress={toggle} />
      {expanded && <View style={styles.expanded} />}
    </View>
  );
};

总结 #

React Native 动画要点:

  • Animated API:内置动画系统
  • Reanimated:更强大的动画库
  • 手势动画:结合 gesture-handler
  • 布局动画:简单的过渡效果
  • 原生驱动:提升动画性能

继续学习 打包发布,了解应用打包流程。

最后更新:2026-03-28