我正在参与「·启航计划」,点击链接了解活动

前言

2021年初,读过一篇关于splash页面动效的推送文章,作者讲解了怎么完成一个闪屏页作用:

将一个英文单词拆分为多个字母,散落在屏幕中,然后依照一定的途径回归,最终展现一段流光作用。

通过自定义View的办法予以完成。

其时我脑中闪过一个念头:他的完成很棒,但假如不需求点触、手势交互,运用Drawable完成更好。并由此编写了一篇文章:三思系列:从头认识Drawable
, 并在不久之后通过 三思系列:为什么要自定义View 一文阐释了对于 “自定义View适用场景” 的个人拙见。

简略通过思想导图回顾 三思系列:从头认识Drawable 一文的内容:

Drawable_guide.png

阅览原文大约需求10-15分钟

文中,咱们最终以该计划完成了 “自定义一个动画Drawable” : unscheduleSelf() / scheduleSelf() 机制 中止回调/设置守时回调 + invalidateSelf() 机制进行改写制作;
计划的实质是 在预设时刻点制作要害帧 。仔细观察后不难发现问题:作用并不顺滑 。作用如下:

视频:链接

彼时,文章的主旨为从头认识Drawable,并未对此展开讨论并进一步优化。 本篇文章作为迟来的续集,将会 对问题展开讨论、探索优化计划、追究原理、并进一步拓展思路。依照此办法展开将迎来久违的三思系列。

关于三思系列

思危:问题实质

上文现已说到,咱们通过 unscheduleSelf() / scheduleSelf() 机制 中止回调/设置守时回调,从头制作要害帧。那么 scheduleSelf() 的实质又是什么?

阅览代码可知,源码中通过接口回调的规划,将功能的完成剥离:

class Drawable {
    public void scheduleSelf(@NonNull Runnable what, long when) {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.scheduleDrawable(this, what, when);
        }
    }
    public final void setCallback(@Nullable Callback cb) {
        mCallback = cb != null ? new WeakReference<>(cb) : null;
    }
    @Nullable
    public Callback getCallback() {
        return mCallback != null ? mCallback.get() : null;
    }
    public interface Callback {
        void invalidateDrawable(@NonNull Drawable who);
        void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);
        void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
    }
}

持续寻觅 Callback 完成类:要点重视 scheduleDrawable 即可

public class View implements Drawable.Callback {
    public void invalidateDrawable(@NonNull Drawable drawable) {
        if (verifyDrawable(drawable)) {
            final Rect dirty = drawable.getDirtyBounds();
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            invalidate(dirty.left + scrollX, dirty.top + scrollY,
                    dirty.right + scrollX, dirty.bottom + scrollY);
            rebuildOutline();
        }
    }
    //看这里
    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
        if (verifyDrawable(who) && what != null) {
            final long delay = when - SystemClock.uptimeMillis();
            if (mAttachInfo != null) {
                mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
                        Choreographer.CALLBACK_ANIMATION, what, who,
                        Choreographer.subtractFrameDelay(delay));
            } else {
                // Postpone the runnable until we know
                // on which thread it needs to run.
                getRunQueue().postDelayed(what, delay);
            }
        }
    }
    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
        if (verifyDrawable(who) && what != null) {
            if (mAttachInfo != null) {
                mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_ANIMATION, what, who);
            }
            getRunQueue().removeCallbacks(what);
        }
    }
    public void unscheduleDrawable(Drawable who) {
        if (mAttachInfo != null && who != null) {
            mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_ANIMATION, null, who);
        }
    }
}

简略解释程序逻辑如下:假如 “该Drawable作用于自身” 且 “Runnable非空”,核算回调的delay,假如View现已添加到Window,则交给Choreographer,否则丢入缓存行列。

而缓存行列的内容将在View添加到Window时交给 Choreographer

public class View {
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //ignore
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        //ignore
    }
}

读者诸君,假如您了解Android的 屏幕改写机制音讯机制 ,一定不会对 Choreographer 感到生疏

Choreographer 直译为编舞者,暗含了 “编制视图改变作用” 的隐喻,其实质依旧是利用 VSync+Handler音讯机制。delay Callback的规划存在毫秒级的误差

作者按:本篇不再展开讨论Android的音讯机制,以下仅给出 根据音讯机制的界面制作规划 要害部分流程图:

ui_msg_img.png

结合前面的代码剖析,scheduleDrawable 的流程能够参阅此图了解。

作者按,尽管仍有差异,但机制一致,可参阅了解

验证

Talk is cheap, show you the code

View 中有一段代码和 scheduleDrawable 高度相似:

class View {
    public void postOnAnimationDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            attachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
                    Choreographer.CALLBACK_ANIMATION, action, null, delayMillis);
        } else {
            // Postpone the runnable until we know
            // on which thread it needs to run.
            getRunQueue().postDelayed(action, delayMillis);
        }
    }
}

留意:scheduleDrawable 根据履行的方针时刻 when,和当时系统时钟核算了delay,又额定调整了delay时刻, Choreographer.subtractFrameDelay(delay),_
它是躲藏API_

public final class Choreographer {
    private static final long DEFAULT_FRAME_DELAY = 10;
    // The number of milliseconds between animation frames.
    private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
    public static long subtractFrameDelay(long delayMillis) {
        final long frameDelay = sFrameDelay;
        return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
    }
}

规划一个简略的验证代码:

class Demo {
    //...
    fun test() {
        val btn = findViewById<Button>(R.id.btn)
        var index = 0
        var s = System.currentTimeMillis()
        val action: Runnable = object : Runnable {
            override fun run() {
                Log.e("lmsg", "$index, offset time ${System.currentTimeMillis() - s - index * 30}")
                index++
                if (index < 100) {
                    btn.postOnAnimationDelayed(
                        this,
                        30L - 10L /*hide api:android.view.Choreographer#subtractFrameDelay*/
                    )
                } else {
                    Log.e("lmsg", "finish, total time ${System.currentTimeMillis() - s}")
                }
            }
        }
        btn.setOnClickListener {
            index = 0
            s = System.currentTimeMillis()
            it.postOnAnimationDelayed(action, 0L)
        }
    }
}

参阅一下成果:留意履行成果不会幂等,但全体表现为超出预期时长

result_1.png

思退:运用Animator改善

Android 在 Android 3.0,API11 中供给了更强壮的动画 Animator,凭借其间的 ValueAnimator,能够很方便的 编列 动画。

即便没有剖析原理,只需运用过特点动画,也知道它具有十分丝滑的作用

以上还都是估测,接下来进行实测。

完成

刨去一致部分,咱们需求完成以下两点:

  • 创立 ValueAnimator 实例,并依照动画需求设置 时长插值器UpdateListener
  • 若没有额定需求,可将 Animatable2 弱化为 Animatable,仅保存动画操控API,通过 ValueAnimator 实例委托完成API事务逻辑。

中心代码如下: 完整代码可从github获取:DrawableWorkShop

class AnimLetterDrawable2 : Drawable(), Animatable {
    // 相似部分省掉
    private val totalFrames = 30 * 3 //3 second, 30frames per second
    private val valueAnimator = ValueAnimator.ofInt(totalFrames).apply {
        duration = 3000L
        this.interpolator = LinearInterpolator()
        addUpdateListener {
            setFrame(it.animatedValue as Int)
        }
    }
    private var frameIndex = 0
    private fun setFrame(frame: Int) {
        if (frame >= totalFrames) {
            return
        }
        frameIndex = frame
        invalidateSelf()
    }
    override fun start() {
        Log.d(tag, "start called")
        valueAnimator.start()
    }
    override fun stop() {
        valueAnimator.cancel()
        setFrame(0)
    }
    override fun isRunning(): Boolean {
        return valueAnimator.isRunning
    }
}

作用和要害代码对比

gif的作用太差,能够在 github项目库房
中获取 webm视频

要害代码差异:

在原计划中,咱们核算了下一帧的播映时刻点,凭借 scheduleSelf -> View#scheduleDrawable 进行了改写

class AnimLetterDrawable {
    private fun setFrame(frame: Int, unschedule: Boolean, animate: Boolean) {
        if (frame >= totalFrames) {
            return
        }
        mAnimating = animate
        frameIndex = frame
        if (unschedule || animate) {
            unscheduleSelf(this)
        }
        if (animate) {
            // Unscheduling may have clobbered these values; restore them
            frameIndex = frame
            scheduleSelf(this, SystemClock.uptimeMillis() + durationPerFrame)
        }
        invalidateSelf()
    }
}

而新计划中,咱们凭借ValueAnimator的更新回调函数直接改写,显示预订帧

class AnimLetterDrawable2 {
    private val valueAnimator = ValueAnimator.ofInt(totalFrames).apply {
        duration = 3000L
        this.interpolator = LinearInterpolator()
        addUpdateListener {
            setFrame(it.animatedValue as Int)
        }
    }
    private fun setFrame(frame: Int) {
        if (frame >= totalFrames) {
            return
        }
        frameIndex = frame
        invalidateSelf()
    }
}

Animator的原理

此刻,再来思索一番,为何 Animator 的完成作用明显丝滑呢?

思危:是否和scheduleDrawable比较运用了不一样的底层机制?

源码跟进

单纯阅览文章内的代码会很枯燥,建议读者诸君对文中列出的源码进行泛读,捉住思路后再精读一遍源码。

以下将有6个要害点,可厘清其原理

  • 1,start办法 — 找到动画被驱动的中心
  • 2, AnimationHandler#addAnimationFrameCallback(AnimationFrameCallback)
  • 3,mAnimationCallbacks 何时移除元素
  • 4,AnimationHandler#doAnimationFrame 办法的逻辑
  • 5,向前看,何人调用FrameCallback — 驱动动画的底层逻辑
  • 6,向后看,ValueAnimator#doAnimationFrame — 丝滑的原因

1,start办法

class ValueAnimator {
    public void start() {
        start(false);
    }
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        //省掉一部分
        addAnimationCallback(0); //这里是中心
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }
    private void addAnimationCallback(long delay) {
        //startWithoutPulsing 才会return
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay); //这里是中心
    }
}

简略阅览,能够排除掉 startAnimation setCurrentPlayTime setCurrentFraction,他们均不是动画回调的中心,只是在进行必要地初始化和FLAG状态维护。

真实的中心是:getAnimationHandler().addAnimationFrameCallback(this, delay);

留意:AnimationHandler 存在线程单例规划:


//运用方:
class ValueAnimator {
    public AnimationHandler getAnimationHandler() {
        return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
    }
}
//ThreadLocal线程单例规划
class 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();
    }
}

2, AnimationHandler#addAnimationFrameCallback(AnimationFrameCallback)

办法逻辑中,有两处需求重视:

  • 假如无 AnimationFrameCallback 回调实例阐明没有在运转中的动画则挂载 Choreographer.FrameCallback mFrameCallback , 为更新动画(_
    调用动画的AnimationFrameCallback回调接口_)做准备。
  • 在动画的 AnimationFrameCallback 回调实例未被注册的情况下,注册该回调实例

看完这一段源码,读者诸君一定会对以下两点发生爱好,咱们在下文展开:

  • doAnimationFrame 办法的逻辑
  • mAnimationCallbacks 何时移除元素

先看源码:

public class AnimationHandler {
    private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            //这不就破案了,只需还有动画的 AnimationFrameCallback,就挂载 mFrameCallback
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };
    private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }
        //留意,delay为0,阅览时能够疏忽这段逻辑
        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }
}

3,mAnimationCallbacks 何时移除元素

AnimationHandler中 “整理” mAnimationCallbacks 的规划 : 先设置null,再择机集中整理null,维护链表结构。能够防止循环过程中移除元素带来的潜在bug、以及防止频频调整链表空间带来的损耗

要害代码为:android.animation.AnimationHandler#removeCallback,它有两处调用点,看完下面这一段源码后再行剖析。

class AnimationHandler {
    public void removeCallback(AnimationFrameCallback callback) {
        mCommitCallbacks.remove(callback);
        mDelayedCallbackStartTime.remove(callback);
        int id = mAnimationCallbacks.indexOf(callback);
        if (id >= 0) {
            mAnimationCallbacks.set(id, null);
            mListDirty = true;
        }
    }
    private void cleanUpList() {
        if (mListDirty) {
            for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
                if (mAnimationCallbacks.get(i) == null) {
                    mAnimationCallbacks.remove(i);
                }
            }
            mListDirty = false;
        }
    }
}

removeCallback 存在一个直接调用,从而可找到两个直接调用点:

  • endAnimation 中止动画时, 主动中止以及核算出动画已结束
  • doAnimationFrame 中发现动画现已被暂停

再看一下源码:

class ValueAnimator {
    private void removeAnimationCallback() {
        if (!mSelfPulse) {
            return;
        }
        //直接调用-1
        getAnimationHandler().removeCallback(this);
    }
    private void endAnimation() {
        if (mAnimationEndRequested) {
            return;
        }
        //直接调用-1
        removeAnimationCallback();
        //省掉
    }
    public final boolean doAnimationFrame(long frameTime) {
        if (mStartTime < 0) {
            // First frame. If there is start delay, start delay count down will happen *after* this
            // frame.
            mStartTime = mReversing
                    ? frameTime
                    : frameTime + (long) (mStartDelay * resolveDurationScale());
        }
        // Handle pause/resume
        if (mPaused) {
            mPauseTime = frameTime;
            //直接调用-2
            removeAnimationCallback();
            return false;
        }
        //略
    }
}

4,AnimationHandler#doAnimationFrame 办法的逻辑

总共有三个事务目的:

  • 筛选,调用回调
  • 处理 CommitCallback 情况
  • 整理 mAnimationCallbacks 详见3
class AnimationHandler {
    private void doAnimationFrame(long frameTime) {
        long currentTime = SystemClock.uptimeMillis();
        final int size = mAnimationCallbacks.size();
        for (int i = 0; i < size; i++) {
            final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
            // `为何会有null?` 请看3 `mAnimationCallbacks` 何时移除元素
            if (callback == null) {
                continue;
            }
            //假如是延迟履行的callback,在未到预守时刻时为false
            if (isCallbackDue(callback, currentTime)) {
                // 回调,实践逻辑:android.animation.ValueAnimator#doAnimationFrame
                callback.doAnimationFrame(frameTime);
                // 此处值得再写一篇文章
                if (mCommitCallbacks.contains(callback)) {
                    getProvider().postCommitCallback(new Runnable() {
                        @Override
                        public void run() {
                            commitAnimationFrame(callback, getProvider().getFrameTime());
                        }
                    });
                }
            }
        }
        cleanUpList();
    }
    private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) {
        if (!mDelayedCallbackStartTime.containsKey(callback) &&
                mCommitCallbacks.contains(callback)) {
            callback.commitAnimationFrame(frameTime);
            mCommitCallbacks.remove(callback);
        }
    }
}

作者按:值得一提的是,AnimationHandler中定义了所谓的 OneShotCommitCallback ,均添加到 mCommitCallbacks中。

ValueAnimator 中曾利用它调整动画开端帧回调

SDK 24 、25 中明确存在,从26直至32均未发现运用。留意,我此次翻阅源码时较为粗略,仍需详查 android.animation.ValueAnimator#addOneShotCommitCallback 方可结论,如有谬误还请读者指出,防止误导。

5,向前看,何人调用FrameCallback

跟进 getProvider().postFrameCallback(mFrameCallback); 发现是暗度陈仓

class AnimationHandler {
    private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }
    private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
        final Choreographer mChoreographer = Choreographer.getInstance();
        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) {
            mChoreographer.postFrameCallback(callback);
        }
        @Override
        public void postCommitCallback(Runnable runnable) {
            mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
        }
        @Override
        public long getFrameTime() {
            return mChoreographer.getFrameTime();
        }
        @Override
        public long getFrameDelay() {
            return Choreographer.getFrameDelay();
        }
        @Override
        public void setFrameDelay(long delay) {
            Choreographer.setFrameDelay(delay);
        }
    }
}

又见 Choreographer ,这回应该不生疏了,跟进代码:

class Choreographer {
    public void postFrameCallback(FrameCallback callback) {
        postFrameCallbackDelayed(callback, 0);
    }
    public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }
        postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
    }
}

值得留意的是:此次运用的是:CALLBACK_ANIMATION

Choreographer 中将Callback总共 分为5类

  • CALLBACK_INPUT = 0;
  • CALLBACK_ANIMATION = 1;
  • CALLBACK_INSETS_ANIMATION = 2;
  • CALLBACK_TRAVERSAL = 3;
  • CALLBACK_COMMIT = 4;

回调时的顺序也是如此。

读者诸君可还记得前文给出的 根据音讯机制处理UI制作 的要害流程图?其间屡次出现要害字样:TRAVERSAL,对应此处的 CALLBACK_TRAVERSAL,它担任界面布局和制作相关的事务。

而在上文 View#scheduleDrawable 的剖析中,发现它运用的类型为:Choreographer.CALLBACK_ANIMATION和 Animator 是一致的!

至此,咱们悬着的心能够放下,Animator 和 View#scheduleDrawable 比较,运用了相同的底层机制

但是咱们的疑问没有得到答案,再顺着整个流程向后看。

6,向后看,ValueAnimator#doAnimationFrame

作者按,以API25之后的源码解析,以下源码为API30,留意24之前、24&25,均存在差异,首要表现为首帧的开端。省掉部分不重要的源码细节

不难发现,要点部分为:animateBasedOnTime(currentTime)

class ValueAnimator {
    public final boolean doAnimationFrame(long frameTime) {
        if (mStartTime < 0) {
            // First frame. If there is start delay, start delay count down will happen *after* this
            // frame.
            mStartTime = mReversing
                    ? frameTime
                    : frameTime + (long) (mStartDelay * resolveDurationScale());
        }
        // Handle pause/resume
        //省掉 暂停、康复的处理
        if (!mRunning) {
            //省掉,判别是否能够开端播映首帧
        }
        if (mLastFrameTime < 0) {
            //省掉,处理动画是否seek的情况
        }
        mLastFrameTime = frameTime;
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        final long currentTime = Math.max(frameTime, mStartTime);
        //此处为要点
        boolean finished = animateBasedOnTime(currentTime);
        //结束的处理
        if (finished) {
            endAnimation();
        }
        return finished;
    }
}

持续捉住要点:animateBasedOnTime(currentTime)

class ValueAnimator {
    boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            //确认lastFraction、fraction
            final long scaledDuration = getScaledDuration();
            //不同在这里 
            final float fraction = scaledDuration > 0 ?
                    (float) (currentTime - mStartTime) / scaledDuration : 1f;
            final float lastFraction = mOverallFraction;
            //确认轮播迭代标记
            final boolean newIteration = (int) fraction > (int) lastFraction;
            final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                    (mRepeatCount != INFINITE);
            // 确认 done
            if (scaledDuration == 0) {
                // 0 duration animator, ignore the repeat count and skip to the end
                done = true;
            } else if (newIteration && !lastIterationFinished) {
                // Time to repeat
                if (mListeners != null) {
                    int numListeners = mListeners.size();
                    for (int i = 0; i < numListeners; ++i) {
                        mListeners.get(i).onAnimationRepeat(this);
                    }
                }
            } else if (lastIterationFinished) {
                done = true;
            }
            //确认fraction 要点1
            mOverallFraction = clampFraction(fraction);
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            //要点2
            animateValue(currentIterationFraction);
        }
        return done;
    }
}

此处有两处要点:

  • 确认 currentIterationFraction
  • animateValue 履行动画帧

看要点1:泛读即可,首要了解fraction的规划

class ValueAnimator {
    private float clampFraction(float fraction) {
        if (fraction < 0) {
            fraction = 0;
        } else if (mRepeatCount != INFINITE) {
            fraction = Math.min(fraction, mRepeatCount + 1);
        }
        return fraction;
    }
    //要点1 整数部分代表iteration,小数部分代表当时iteration的fraction
    private float getCurrentIterationFraction(float fraction, boolean inReverse) {
        fraction = clampFraction(fraction);
        int iteration = getCurrentIteration(fraction);
        float currentFraction = fraction - iteration;
        return shouldPlayBackward(iteration, inReverse)
                ? 1f - currentFraction
                : currentFraction;
    }
    //根据是fraction和iteration的规划:
    //    Calculates current iteration based on the overall fraction. 
    //    The overall fraction will be in the range of [0, mRepeatCount + 1]. 
    //    Both current iteration and fraction in the current iteration can be derived from it.
    private int getCurrentIteration(float fraction) {
        fraction = clampFraction(fraction);
        // If the overall fraction is a positive integer, we consider the current iteration to be
        // complete. In other words, the fraction for the current iteration would be 1, and the
        // current iteration would be overall fraction - 1.
        double iteration = Math.floor(fraction);
        if (fraction == iteration && fraction > 0) {
            iteration--;
        }
        return (int) iteration;
    }
    //和动画正向、反向播映有关,可先疏忽
    private boolean shouldPlayBackward(int iteration, boolean inReverse) {
        if (iteration > 0 && mRepeatMode == REVERSE &&
                (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
            // if we were seeked to some other iteration in a reversing animator,
            // figure out the correct direction to start playing based on the iteration
            if (inReverse) {
                return (iteration % 2) == 0;
            } else {
                return (iteration % 2) != 0;
            }
        } else {
            return inReverse;
        }
    }
}

看要点2:

class ValueAnimator {
    void animateValue(float fraction) {
        //插值器从头核算fraction -- 优雅的规划
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        //PropertyValuesHolder 核算value -- 又是一个优雅的规划
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        //回调,onAnimationUpdate 常用到 getAnimatedValue,和 calculateValue 对应
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }
}

阶段性小结

源码内容着实很多,通过刚才的源码要点拆解,也已梳理出大致流程。

回归到咱们阅览源码前的问题:

Animator 是否和scheduleDrawable比较运用了不一样的底层机制?

否, 均运用了 Choreographer [ˌkɔːriˈɑːɡrəfər],记住它的读写 + Vsync + Android 音讯机制 ,且回调类型一致,均为 CALLBACK_ANIMATION

为何更加丝滑?

动画内部调用频次 ≥ 原计划,回调时根据时刻核算帧号的算法更加精确合理

ValueAnimator#animateBasedOnTime 中,运用了精确、合理的核算办法 :final float fraction = scaledDuration > 0 ? (float) (currentTime - mStartTime) / scaledDuration : 1f;

而先前文章中的代码,并没有根据当时实践时刻调整帧。

思变:翻开思路

至此,动画的中心奥秘现已揭开,似乎全部已尽在不言中,轮子也均已齐备,也并不需求再额定完成一套插值器、估值器逻辑。

既然如此,咱们不再对第一篇中的比如进行以下改善:”根据时刻调整帧”,”提升回调频率”。

作者按:假如下次计划写插值器、估值器的文章,可能以逐步完善造轮子的办法进行内容展开

那么本篇的中心内容,除了面试或者给搭档科普外,还能带来什么呢?

全体回顾一下,并翻开思路:

  1. 咱们从一个实例出发进行完善,并收成一个经验:能够通过 Drawable+Animator,将动画内容推行到恣意View做显示,假如没有必要,能够少做一些自定义View的工作
  2. 剖析了Drawable更新内容的底层完成,是否能够将这种动画作用推行到更多当地呢?例如 TextView的DrawableStartImageSpan是否都能正确显示动效呢?,假如不能要怎么做?
  3. 咱们剖析动画被驱动的过程中,遇到一个瑰宝 Choreographer,是否能够拿来干点有趣的工作?例如:FPS监测
  4. 将ValueAnimator的中心机制复刻,在其他平台搞点好玩的东西😂
  5. 视觉呈现内容时刻 的函数关系确守时,运用 ValueAnimator 作为中心驱动,将问题变为一个纯数学问题,例如 点迹动效制作全景图锚点A到锚点B之间的渐变
  6. 交融以上内容,自定义一套数据协议,解析后,所见皆可动起来

闲话一二

文中出现的源码,除去AOSP部分,均收录于库房中:DrawableWorkShop

最近还处于瓶颈之中,我花了大约半年的时刻,让自己 “慢” 下来,却还没有做到真实松弛下来,礼记中言:”张而不弛,文武弗能也;弛而不张,文武弗为也;以逸待劳,文武之道也。”

有两个方面的瓶颈,让我较为难过:

  • “输出质量的高期望” 与 “输入、常识体系存货达不到更高层次” 之间的对立带来的内容瓶颈
  • “不同读者需求的常识深度不同” 与 “博客内容提纲不能照顾到各个深度” 之间的对立带来的编写瓶颈

我还需求调整好节奏、捋一捋下一个五年,再进行常识重整合,才干先驰后张,输出更有意义的内容,这能解决第一个瓶颈问题。但第二个瓶颈问题,的确没找到办法。