在React Native中创建一个动画有多种方式。
使用react-native
包的Animated
组件创建动画
使用react-native
包的LayoutAnimation
创建布局动画
使用Web API标准的requestAnimationFrame
控制动画
以上是目前常用的几种在React Native中创建动画的方式,Animated动画可以进行nativeDriver加速,但是如果属性不支持,将会使用requestAnimationFrame
实现计算。LayoutAnimation控制粒度不及Animated细。
本文主要来说说Animated.Value及Animated.ValueXY
使用Animated创建一个动画 先通过使用Animated创建一个动画回顾一下React Native中Animated的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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的方法创建一个动画对象常用的方法有Animated.decay
、Animated.timing
、Animated.spring
等
调用第三步生成对象的start()
方法即可触发动画
Animated.timing()方法 在Animated创建动画的方法中,timing是最简单易懂的一个,这里看一下timing方法内部都做了什么。
注意:文中的源码基于0.55.4
版本的react-native源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 => { this ._updateValue(value, true ); }, result => { this ._animation = null ; if (handle !== null ) { InteractionManager.clearInteractionHandle(handle); } callback && callback(result); }, previousAnimation, this , ); }
这个方法通过调用传入的animation.start
方法处理值更改,当值update时调用自身的_updateValue
最后会调用到_flush
,这个方法是一个关键方法,阅读以下它的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function _flush (rootNode: AnimatedValue ): void { const animatedStyles = new Set (); function findAnimatedStyles (node ) { if (typeof node.update === 'function' ) { animatedStyles.add(node); } else { node.__getChildren().forEach(findAnimatedStyles); } } findAnimatedStyles(rootNode); animatedStyles.forEach(animatedStyle => animatedStyle.update()); }
它会遍历所有子节点,找出带有update方法的节点,并调用他们的update方法。好了,Animated.Value的代码阅读展示到这里,接下来看看createAnimatedComponent
这个方法主要做了什么。
createAnimatedComponent 高阶组件 打开它的源码可以看到它是一个高阶组件,它返回的组件中有一个_propsAnimated
的变量,在UNSAFE_componentWillMount
中调用_attachProps
进行初始化,传入了props和一个函数_animatedPropsCallback
,这个传入函数的作用是通过setNativeProps
更新最终的Component。来看一下AnimatedProps
的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class AnimatedProps extends AnimatedNode {true... true__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、事件通知等。