React Native 提供了两个互补的动画系统:用于创建精细的交互控制的动画Animated
和用于全局的布局动画LayoutAnimation
。
Animated
Animated
使得开发者可以非常容易地实现各种各样的动画和交互方式,并且具备极高的性能。Animated
旨在以声明的形式来定义动画的输入与输出,在其中建立一个可配置的变化函数,然后使用简单的start/stop
方法来控制动画按顺序执行。
Animated
仅封装了6个可以动画化的组件:View
、Text
、Image
、ScrollView
、FlatList
和SectionList
,不过你也可以使用Animated.createAnimatedComponent()
来封装你自己的组件。
下面是一个在加载时带有淡入动画效果的视图:
import React, { useState, useEffect } from 'react';
import { Animated, Text, View } from 'react-native';
const FadeInView = (props) => {
const [fadeAnim] = useState(new Animated.Value(0)) // 透明度初始值设为0
React.useEffect(() => {
Animated.timing( // 随时间变化而执行动画
fadeAnim, // 动画中的变量值
{
toValue: 1, // 透明度最终变为1,即完全不透明
duration: 10000, // 让动画持续一段时间
}
).start(); // 开始执行动画
}, [])
return (
{props.children}
);
}
// 然后你就可以在组件中像使用`View`那样去使用`FadeInView`了
export default () => {
return (
Fading in
)
}
我们来分解一下这个过程。在FadeInView
的构造函数里,我们创建了一个名为fadeAnim
的Animated.Value
,并放在state
中。而View
的透明度是和这个值绑定的。
组件加载时,透明度首先设为 0。然后一个easing动画开始改变fadeAnim
值,同时会导致所有与其相关联的值(本例中是透明度)也逐帧更新,最终和fadeAnim
一样变为1。
这一过程经过特别优化,执行效率会远高于反复调用setState
和多次重渲染。
因为这一过程是纯声明式的,因此还有进一步优化的空间,比如我们可以把这些声明的配置序列化后发送到一个高优先级的线程上运行。
动画拥有非常灵活的配置项。自定义的或预定义的 easing 函数、延迟、持续时间、衰减系数、弹性常数等都可以在对应类型的动画中进行配置。
Animated
提供了多种动画类型,其中最常用的要属Animated.timing()
.
Animated.timing(this.state.xPosition, {
toValue: 100,
easing: Easing.back(),
duration: 2000
}).start();
多个动画可以通过parallel
(同时执行)、sequence
(顺序执行)、stagger
和delay
来组合使用。它们中的每一个都接受一个要执行的动画数组,并且自动在适当的时候调用start/stop
。
Animated.sequence([
// decay, then spring to start and twirl
Animated.decay(position, {
// coast to a stop
velocity: { x: gestureState.vx, y: gestureState.vy }, // velocity from gesture release
deceleration: 0.997
}),
Animated.parallel([
// after decay, in parallel:
Animated.spring(position, {
toValue: { x: 0, y: 0 } // return to start
}),
Animated.timing(twirl, {
// and twirl
toValue: 360
})
])
]).start(); // start the sequence group
默认情况下,如果任何一个动画被停止或中断了,组内所有其它的动画也会被停止。Parallel 有一个stopTogether
属性,如果设置为false
,可以禁用自动停止。
在Animated
文档的组合动画一节中列出了所有的组合方法。
LayoutAnimation
APILayoutAnimation
允许你在全局范围内创建
和更新
动画,这些动画会在下一次渲染或布局周期运行。它常用来更新 flexbox 布局,因为它可以无需测量或者计算特定属性就能直接产生动画。尤其是当布局变化可能影响到父节点(譬如“查看更多”展开动画既增加父节点的尺寸又会将位于本行之下的所有行向下推动)时,如果不使用LayoutAnimation
,可能就需要显式声明组件的坐标,才能使得所有受影响的组件能够同步运行动画。
注意尽管LayoutAnimation
非常强大且有用,但它对动画本身的控制没有Animated
或者其它动画库那样方便,所以如果你使用LayoutAnimation
无法实现一个效果,那可能还是要考虑其他的方案。
另外,如果要在Android上使用 LayoutAnimation,那么目前还需要在UIManager
中启用::
// 在执行任何动画代码之前,比如在入口文件App.js中执行
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
import React from 'react';
import {
NativeModules,
LayoutAnimation,
Text,
TouchableOpacity,
StyleSheet,
View,
} from 'react-native';
const { UIManager } = NativeModules;
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
export default class App extends React.Component {
state = {
w: 100,
h: 100,
};
_onPress = () => {
// Animate the update
LayoutAnimation.spring();
this.setState({w: this.state.w + 15, h: this.state.h + 15})
}
render() {
return (
Press me!
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 200,
height: 200,
backgroundColor: 'red',
},
button: {
backgroundColor: 'black',
paddingHorizontal: 20,
paddingVertical: 15,
marginTop: 15,
},
buttonText: {
color: '#fff',
fontWeight: 'bold',
},
});