Skip to content

React Native - 动画

React Native 提供了强大的 API 用于创建动画。本章节重点介绍 Animated API,用于对动画值进行精细控制,并简要提及用于简单过渡的 LayoutAnimation。我们将使用函数组件和 Hooks。

Animated API 允许你创建基于时间的动画(例如 Animated.timing)、基于弹簧的动画(Animated.spring)或衰减动画(Animated.decay)。通常你会对特殊的 Animated 组件(例如 Animated.View、Animated.Text、Animated.Image)的特定样式属性进行动画处理。

我们将创建一个示例,演示如何通过按下操作改变一个盒子的宽度和高度。

AnimatedBox.js

import React, { useRef, useState } from 'react';
import { View, StyleSheet, Animated, TouchableOpacity, Text } from 'react-native';
const AnimatedBox = () => {
// 使用 useRef 存储 Animated 值,使其在多次渲染之间保持不变
const animatedWidth = useRef(new Animated.Value(100)).current;
const animatedHeight = useRef(new Animated.Value(100)).current;
const animatedOpacity = useRef(new Animated.Value(1)).current;
const [isExpanded, setIsExpanded] = useState(false);
const animateBox = () => {
const newWidth = isExpanded ? 100 : 200;
const newHeight = isExpanded ? 100 : 200;
const newOpacity = isExpanded ? 1 : 0.5;
// 并行动画
Animated.parallel([
Animated.timing(animatedWidth, {
toValue: newWidth,
duration: 500, // 毫秒
useNativeDriver: false, // 对于非布局属性,设置为 'true' 可获得更好的性能
}),
Animated.timing(animatedHeight, {
toValue: newHeight,
duration: 700,
useNativeDriver: false,
}),
Animated.timing(animatedOpacity, {
toValue: newOpacity,
duration: 700,
useNativeDriver: true, // 透明度可以使用原生驱动
})
]).start(() => {
setIsExpanded(!isExpanded); // 动画完成后切换状态
});
};
// 创建一个动画样式对象
const animatedStyle = {
width: animatedWidth,
height: animatedHeight,
opacity: animatedOpacity,
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={animateBox}>
<Animated.View style={[styles.box, animatedStyle]} />
</TouchableOpacity>
<Text style={styles.instructionText}>
Tap the box to {isExpanded ? 'collapse' : 'expand'} it.
</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f0f0f0',
},
box: {
backgroundColor: 'royalblue',
// 初始尺寸由 Animated.Value 设置
},
instructionText: {
marginTop: 20,
fontSize: 16,
color: '#333',
}
});
export default AnimatedBox;
  • useRef 和 Animated.Value:我们在 useRef 内部初始化 Animated.Value,以确保它们在多次渲染之间保持不变,不会被重新创建。animatedWidth.current 提供了对 Animated.Value 实例的访问。
  • Animated.timing:创建一个动画,在指定的持续时间内过渡一个值。其他类型包括 Animated.spring(基于物理的弹簧动画)和 Animated.decay(初始速度逐渐减慢直至停止)。
  • Animated.parallel:同时运行多个动画。其他组合器包括 Animated.sequence(动画一个接一个按顺序运行)和 Animated.stagger(错开起始时间的动画)。
  • start():开始动画。它可以接受一个可选的回调函数,在动画完成时触发。
  • Animated.View:一个特殊的组件,其样式属性可以被动画化。你必须使用 Animated.View、Animated.Text、Animated.Image 等,或者使用 Animated.createAnimatedComponent() 创建自己的可动画组件。
  • useNativeDriver: true:为了提高性能,将动画卸载到原生线程执行。这仅适用于非布局属性(如 transform、opacity)。布局属性(width、height、margin、padding、top、left 等)必须使用 useNativeDriver: false(默认值)并在 JS 线程上运行。

当你点击蓝色方块时,它的宽度、高度和透明度会平滑地动画变化。再次点击将反转动画。

LayoutAnimation 提供了一种更简单的方式来动画处理布局属性变化时的过渡(例如,由于状态更新引起)。它会在下次布局计算发生时,将视图动画移动到新位置。

LayoutAnimationExample.js

import React, { useState } from 'react';
import {
View, StyleSheet, TouchableOpacity, Text,
LayoutAnimation, UIManager, Platform
} from 'react-native';
// 为 Android 启用 LayoutAnimation(iOS 默认已启用)
if (Platform.OS === 'android') {
if (UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
const LayoutAnimationExample = () => {
const [boxSize, setBoxSize] = useState(100);
const [isToggled, setIsToggled] = useState(false);
const toggleBox = () => {
// 配置动画类型。
// 选项:LayoutAnimation.Presets.easeInEaseOut, .linear, .spring
// 或定义自定义动画:LayoutAnimation.configureNext(config);
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
setBoxSize(isToggled ? 100 : 200);
setIsToggled(!isToggled);
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={toggleBox}>
<View style={[styles.box, { width: boxSize, height: boxSize }]} >
<Text style={styles.boxText}>Tap Me</Text>
</View>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5fcff',
},
box: {
backgroundColor: 'mediumseagreen',
justifyContent: 'center',
alignItems: 'center',
},
boxText: {
color: 'white',
fontWeight: 'bold'
}
});
export default LayoutAnimationExample;

LayoutAnimation 对于全局布局变化更简单,但不如 Animated 灵活。你需要在引起重新布局的状态变化之前调用 LayoutAnimation.configureNext()。

  • 当需要进行复杂、交互式动画,或需要对动画值和序列进行细粒度控制时,使用 Animated。
  • 对于由状态变化触发的简单、即发即忘的布局属性动画,使用 LayoutAnimation。
  • 对于更高级、更高性能的动画,特别是基于手势的动画,可以考虑使用 React Native Reanimated 和 React Native Gesture Handler 等库。

查阅官方动画指南:https://reactnative.dev/docs/animations 和 https://reactnative.dev/docs/layoutanimation。