上次更新: 2018-05-24 17:52:51
标签: 前端
在React Native中创建一个动画有多种方式。
react-native
包的Animated
组件创建动画react-native
包的LayoutAnimation
创建布局动画requestAnimationFrame
控制动画以上是目前常用的几种在React Native中创建动画的方式,Animated动画可以进行nativeDriver加速,但是如果属性不支持,将会使用requestAnimationFrame
实现计算。LayoutAnimation控制粒度不及Animated细。
本文主要来说说Animated.Value及Animated.ValueXY
先通过使用Animated创建一个动画回顾一下React Native中Animated的使用。
class AnimatedPlayground extends Component {
state = {
size: new Animated.Value(50) // 动画属性初始值
}
componentDidMount () {
Animated.timing(this.state.size, {
toValue: 200 // 下一阶段值
}).start() // 开始动画
}
render () {
return (
<Animated.View style={{
backgroundColor: '#000',
width: this.state.size, // 将动画值赋给元素
height: this.state.size
}}/>
)
}
}
使用Animated做动画需要注意几个地方
Animated.Value
或者Animated.ValueXY
初始化。Animated
的组件,Animated提供了Animated.View
、Animated.Text
、Animated.Image
和Animated.ScrollView
,其他组件通过Animated.createAnimatedComponent()
方法处理后即可使用Animated.Value值作为属性Animated.decay
、Animated.timing
、Animated.spring
等start()
方法即可触发动画在Animated创建动画的方法中,timing是最简单易懂的一个,这里看一下timing方法内部都做了什么。
注意:文中的源码基于
0.55.4
版本的react-native源码
// react-native/Libraries/Animated/src/AnimatedImplementation.js#181
const timing = function(
value: AnimatedValue | AnimatedValueXY,
config: TimingAnimationConfig,
): CompositeAnimation {
const start = function(
animatedValue: AnimatedValue | AnimatedValueXY,
configuration: TimingAnimationConfig,
callback?: ?EndCallback,
): void {
callback = _combineCallbacks(callback, configuration);
const singleValue: any = animatedValue;
const singleConfig: any = configuration;
singleValue.stopTracking();
if (configuration.toValue instanceof AnimatedNode) {
singleValue.track(
new AnimatedTracking(
singleValue,
configuration.toValue,
TimingAnimation,
singleConfig,
callback,
),
);
} else {
singleValue.animate(new TimingAnimation(singleConfig), callback);
}
};
return (
maybeVectorAnim(value, config, timing) || {
start: function(callback?: ?EndCallback): void {
start(value, config, callback);
},
stop: function(): void {
value.stopAnimation();
},
reset: function(): void {
value.resetAnimation();
},
_startNativeLoop: function(iterations?: number): void {
const singleConfig = {...config, iterations};
start(value, singleConfig);
},
_isUsingNativeDriver: function(): boolean {
return config.useNativeDriver || false;
},
}
);
};
timing方法会返回一个对象,在返回前会尝试使用maybeVectorAnim
进行坐标值的准换,如果值不是坐标值,将会返回一个对象。在上面的例子中,对timing返回的对象调用start()
方法会开始动画,所以start方法是触发动画的关键。跟随代码可以找到最后会调用Animated.Value的animate
方法。
// react-native/Libraries/Animated/src/nodes/AnimatedValue.js#278
animate(animation: Animation, callback: ?EndCallback): void {
let handle = null;
if (animation.__isInteraction) {
handle = InteractionManager.createInteractionHandle();
}
const previousAnimation = this._animation;
this._animation && this._animation.stop();
this._animation = animation;
animation.start(
this._value,
value => {
// Natively driven animations will never call into that callback, therefore we can always
// pass flush = true to allow the updated value to propagate to native with setNativeProps
this._updateValue(value, true /* flush */);
},
result => {
this._animation = null;
if (handle !== null) {
InteractionManager.clearInteractionHandle(handle);
}
callback && callback(result);
},
previousAnimation,
this,
);
}
这个方法通过调用传入的animation.start
方法处理值更改,当值update时调用自身的_updateValue
最后会调用到_flush
,这个方法是一个关键方法,阅读以下它的实现
// react-native/Libraries/Animated/src/nodes/AnimatedValue.js#51
function _flush(rootNode: AnimatedValue): void {
const animatedStyles = new Set();
function findAnimatedStyles(node) {
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.68 was deployed. To see the error delete this
* comment and run Flow. */
if (typeof node.update === 'function') {
animatedStyles.add(node);
} else {
node.__getChildren().forEach(findAnimatedStyles);
}
}
findAnimatedStyles(rootNode);
/* $FlowFixMe */
animatedStyles.forEach(animatedStyle => animatedStyle.update());
}
它会遍历所有子节点,找出带有update方法的节点,并调用他们的update方法。好了,Animated.Value的代码阅读展示到这里,接下来看看createAnimatedComponent
这个方法主要做了什么。
打开它的源码可以看到它是一个高阶组件,它返回的组件中有一个_propsAnimated
的变量,在UNSAFE_componentWillMount
中调用_attachProps
进行初始化,传入了props和一个函数_animatedPropsCallback
,这个传入函数的作用是通过setNativeProps
更新最终的Component。来看一下AnimatedProps
的实现
// react-native/Libraries/Animated/src/nodes/AnimatedProps.js
class AnimatedProps extends AnimatedNode {
...
__attach(): void {
for (const key in this._props) {
const value = this._props[key];
if (value instanceof AnimatedNode) {
value.__addChild(this);
}
}
}
...
update(): void {
this._callback();
}
}
里面的update
方法将会调用传入的回调,回到上面说的_flush
方法,可以大概知道了Animated的原理了。但是这里还有一个问题就是什么时候将Value和组件关联起来的,就是为什么_flush
能找到AnimatedProps的update方法。
上面代码中我贴出了__attach
方法,改方法会在初始化的时候调用,最终会把this传入value.__addChild
,这里的value就是通过props传入的Animated.Value。在_flush
中会调用node.__getChildren().forEach(findAnimatedStyles);
,它将会返回在前面添加的Child。
到这里就能理解在React Native的Animated动画实现流程了。
文章只是简单分析了一下部分源码,并没有完全分析,源码中还有很多优化及功能代码,比如detach、事件通知等。