ReactNative动画效果分析(仅从Android端源代码进行分析)

如何查看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事件,性能很高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值