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