专栏名称: 蚂蚁金服ProtoTeam
数据前端团队
目录
相关文章推荐
51好读  ›  专栏  ›  蚂蚁金服ProtoTeam

属性动画源码分析

蚂蚁金服ProtoTeam  · 掘金  · 前端  · 2017-12-08 05:47

正文

请到「今天看啥」查看全文


Evaluator.evaluate(float fraction, Number startValue, Number endValue)

如果我们传入了
ObjectAnimator.ofFloat(animationView, "X", 0, 100, 200, 500);

我们传入 4 个值,那么我们的动画将分为 3 个部分

    0 ~ 100 一个阶段, fraction 从 0.0 ~ 1.0;
        startValue = 0,endValue =100
    100 ~ 200 一个阶段, fraction 从 0.0 ~ 1.0;
        startValue = 100,endValue =200
    200 ~ 500 一个阶段, fraction 从 0.0 ~ 1.0;
        startValue = 200,endValue =500

然后 FloatPropertyValuesHolder 创建部分完毕,一层一层向上返回。

(O__O "…,如果忘记这个部分在哪,可以往上翻去看看)

然后执行 ObjectAnimator.setValues() ,ObjectAnimator 直接调用了父类的 ValueAnimator.setValues()

public void setValues(PropertyValuesHolder... values) {
    int numValues = values.length;
    mValues = values;
    mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
    for (int i = 0; i < numValues; ++i) {
        PropertyValuesHolder valuesHolder = values[i];
        mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
    }
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
}

然后我们看到 mValues 和 mValuesMap 赋值操作了。mValues 和 mValuesMap 持有的对象就是我们创建的 FloatPropertyValuesHolder

终于我们完成了 ObjectAnimator 创建过程的源代码追踪,总结如下

1. 创建 ObjectAnimator 对象,保存 target 和 propertyName
2. 根据传入的 values 创建一组关键帧。
3. 关键帧封装到 FloatPropertyValuesHolder 中。
4. FloatPropertyValuesHolder 交给 mValues 和 mValuesMap 持有

总结一下整个流程

animation_01.png

开启动画

这一步是整个属性动画中最麻烦的操作,一层一层的剥开源码,搞得我眼睛干涩,脾气暴躁一度想放弃。

所以先做个深呼吸~,准备迎接更虐心的源码追踪。

objectAnimator.start()

public void start() {
    AnimationHandler.getInstance().autoCancelBasedOn(this);
    ……
    super.start();
}

这里有两句关键代码,其他代码没有贴出来。第一句检测如果动画已经执行,则停止动画。

如果不理解,并不重要。因为第一次调用的时候,肯定没有动画在执行。等看完整个过程就明白这一句的意思了。

然后我们发现又双叒叕调用了父类的方法。

public void start() {
    start(false);
}

然后加了一个 false 参数继续调用同名函数

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mSelfPulse = !mSuppressSelfPulseRequested;
    …… 这里的代码此时不会执行,先省略
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;
    // Resets mLastFrameTime when start() is called, so that if the animation was running,
    // calling start() would put the animation in the
    // started-but-not-yet-reached-the-first-frame phase.
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    addAnimationCallback(0);

    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        // If there's no start delay, init the animation and notify start listeners right away
        // to be consistent with the previous behavior. Otherwise, postpone this until the first
        // frame after the start delay.
        startAnimation();
        if (mSeekFraction == -1) {
            // No seek, start at play time 0. Note that the reason we are not using fraction 0
            // is because for animations with 0 duration, we want to be consistent with pre-N
            // behavior: skip to the final value immediately.
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

start(boolean playBackwards) 方法比较长,仔细咀嚼改方法后,找到三个地方比较重要

1. addAnimationCallback(0)
2. startAnimation()
3. setCurrentPlayTime(0)/setCurrentFraction(mSeekFraction) 其实是一个方法,因为 setCurrentPlayTime 会调用 setCurrentFraction(mSeekFraction)

这三个地方『1』是最负责的一步,但是又是最重要的一步,再做一次深呼吸,然后准备跟下去!

addAnimationCallback(0)
private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}

这里一下子出现两个方法调用,先看下 getAnimationHandler()

   public AnimationHandler getAnimationHandler() {
       return AnimationHandler.getInstance();
   }

好像是获取一个 AnimationHandler 对象。似乎还是一个单利

public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
private boolean mListDirty = false;

public static AnimationHandler getInstance() {
    if (sAnimatorHandler.get() == null) {
        sAnimatorHandler.set(new AnimationHandler());
    }
    return sAnimatorHandler.get();
}

原来 AnimationHandler 用 ThreadLocal 保证每个线程只有一个实例。做到线程中单例。

然后看下 AnimationHandler.addAnimationFrameCallback()

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        getProvider().postFrameCallback(mFrameCallback);
    }
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }

    if (delay > 0) {
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
    }
}

执行动画的时候 mAnimationCallbacks.size() = 0 所以会执行(这里稍微留意一下传入的 callback)

getProvider().postFrameCallback(mFrameCallback)

看下 getProvider()

private AnimationFrameCallbackProvider getProvider() {
    if (mProvider == null) {
        mProvider = new MyFrameCallbackProvider();
    }
    return mProvider;
}

返回了一个 MyFrameCallbackProvider() 对象,而且改对象的构造方法并无特别之处。然后我们在看下 mFrameCallback 对象。

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        if (mAnimationCallbacks.size() > 0) {
            getProvider().postFrameCallback(this);
        }
    }
};

mFrameCallback 又是一个回调方法,而且 doFrame() 方法中好像有传入了自身。







请到「今天看啥」查看全文