如何查看Android端动画源码:
首先打开nodeModule-react-native-ReactAndroid-src-main-java-com-facebook-react
就可以查看到ReactNative Android端的动画源码
可以看到StyleAnimatedNode和TransformAnimatedNode 都是继承自AnimatedNode
回过头来看ReactNative如何写一个动画效果
Animated
Animated适用于更细微的变化过程动画,可适配性更高。如下简单使用
export default class Test extends Component {
state = {
bounceValue: new Animated.Value(0),
};
render() {
return (
<Animated.Image source={require('./my-icon.png')} style={{
transform: [{scale: this.state.bounceValue}]
}}/>)
}
componentDidMount() {
Animated.timing(this.state.bounceValue, {
toValue: 3,
duration: 3000,
// useNativeDriver: true 这里先注释掉,标记为注释@1
}).start()
}
}
通过查看AnimatedImplementation.js文件可以查看到动画源码,篇幅有限仅展示核心代码
所有Animated的js源码都在AnimatedImplementation.js中,本文RN版本为0.57.8.
首先看下动画的入口函数:Animated.timing
该函数定义在181行,不同RN版本对应的行数不一致,请以自己的版本为准。
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);
}
};
这里的singleValue是传入的第一个参数this.state.bounceValue,为AnimatedValue类型。
这里的singleConfig是传入的第二个参数。
首先分析下第一个参数的AnimatedValue这个类型的animate方法:
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方法,这个参数就是上面传过来的new TimingAnimation(singleConfig)对象。TimingAnimation的start方法如下:
start(
fromValue: number,
onUpdate: (value: number) => void,
onEnd: ?EndCallback,
previousAnimation: ?Animation,
animatedValue: AnimatedValue,
): void {
this.__active = true;
this._fromValue = fromValue;
this._onUpdate = onUpdate;
this.__onEnd = onEnd;
const start = () => {
// Animations that sometimes have 0 duration and sometimes do not
// still need to use the native driver when duration is 0 so as to
// not cause intermixed JS and native animations.
if (this._duration === 0 && !this._useNativeDriver) {
this._onUpdate(this._toValue);
this.__debouncedOnEnd({finished: true});
} else {
this._startTime = Date.now();
if (this._useNativeDriver) {
this.__startNativeAnimation(animatedValue);
} else {
this._animationFrame = requestAnimationFrame(
this.onUpdate.bind(this),
);
}
}
};
if (this._delay) {
this._timeout = setTimeout(start, this._delay);
} else {
start();
}
}
这里出现了分支,当注释@1打开时,_useNativeDriver为true,动画的执行方式将交由原生端,不再走下边的js端。
原生端实现:
可以看到js调用了__startNativeAnimation方法,我们看下__startNativeAnimation
__startNativeAnimation(animatedValue: AnimatedValue): void {
animatedValue.__makeNative();
this.__nativeId = NativeAnimatedHelper.generateNewAnimationId();
NativeAnimatedHelper.API.startAnimatingNode(
this.__nativeId,
animatedValue.__getNativeTag(),
this.__getNativeAnimationConfig(),
this.__debouncedOnEnd.bind(this),
);
}
我们着重看下这个方法NativeAnimatedHelper.API.startAnimatingNode:
startAnimatingNode: function(
animationId: ?number,
nodeTag: ?number,
config: Object,
endCallback: EndCallback,
): void {
assertNativeAnimatedModule();
NativeAnimatedModule.startAnimatingNode(
animationId,
nodeTag,
config,
endCallback,
);
},
我们可以看到内部执行了NativeAnimatedModule.startAnimatingNode,熟悉Android开发的同学可能知道这是调用了Android端的方法,那我们去Android端看下这个NativeAnimatedModule
这里边包含了Android与JS动画交互的方法,有兴趣的同学可以看下这个文件,该文件在这个包下
package com.facebook.react.animated;
我们看下startAnimatingNode方法:
@ReactMethod
public void startAnimatingNode(
final int animationId,
final int animatedNodeTag,
final ReadableMap animationConfig,
final Callback endCallback) {
mOperations.add(new UIThreadOperation() {
@Override
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
animatedNodesManager.startAnimatingNode(
animationId,
animatedNodeTag,
animationConfig,
endCallback);
}
});
}
我们看下NativeAnimatedNodesManager下的startAnimatingNode方法:
js端实现:
可以看到js端调用了requestAnimationFrame执行onUpdate方法,
requestAnimationFrame是跟原生端的定时器通信,让原生端定时器触发回调事件onUpdate
onUpdate(): void {
const now = Date.now();
if (now >= this._startTime + this._duration) {// 动画时间结束调用
if (this._duration === 0) {
this._onUpdate(this._toValue);
} else {
this._onUpdate(
this._fromValue + this._easing(1) * (this._toValue - this._fromValue),
);
}
this.__debouncedOnEnd({finished: true});
return;
}
// 在这里更新传入的this.state.bounceValue的值
this._onUpdate(
this._fromValue +
this._easing((now - this._startTime) / this._duration) *
(this._toValue - this._fromValue),
);
if (this.__active) {
// 不断注册原生端定时器事件,循环绑定
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
}
}
这个方法内部会不断注册原生端定时器事件,回调自身循环更新this.state.bounceValue的值,直到动画时间结束退出。这里要注意,每一帧都会调用this._onUpdate方法。_onUpdate是start方法的第二个参数,由上一个方法传递过来的,最终执行的是AnimatedValue的_updateValue方法。
_updateValue(value: number, flush: boolean): void {
this._value = value;
if (flush) {
_flush(this);
}
for (const key in this._listeners) {
this._listeners[key]({value: this.__getValue()});
}
}
方法传递到_flush,再到AnimatedProps的update方法,最终回调到AnimatedComponent的_attachProps方法中注册的回调callback。(AnimatedComponent就是我们使用的Animated.Image)
var callback = () => {
if (this._component.setNativeProps) {
if (!this._propsAnimated.__isNative) {
this._component.setNativeProps(this._propsAnimated.__getAnimatedValue());
} else {
throw new Error('Attempting to run JS driven animation on animated '
+ 'node that has been moved to "native" earlier by starting an '
+ 'animation with `useNativeDriver: true`');
}
} else {
this.forceUpdate();
}
};
this._component为Image,这里会判断下我们使用的组件是否能够设置setNativeProps,只有那些能够设置该属性的组件,如Image、Text、Image、ScrollView才能使用Animated动画,动画的呈现原因是利用定时器不断改变组件的setNativeProps值,可避免触发全局的render事件,性能很高。