Animator

作者: ZDCrazy | 来源:发表于2018-06-03 20:19 被阅读83次

  最近由于项目需要,写了一个动画。历经坎坷,花了好几天的时间,被 UX 批评了好几次,事后回想,发现这个动画其实也比较简单,无非就是对一系列动作的拆解工作,形成一系列的 animator , 然后按照时间顺序执行就可以了,动画效果图如下:


boost_final.gif

  虽然写的这个动画比较简单,但是在写动画过程中了解了一些 Animator 和 Animation 的原理,在此记录一下,毕竟温故知新,可以为师矣。本篇涉及到的Android 源码 7.0 进行分析。

Animator

  以 ValueAnimator 为例,从 start() 方法开始分析,代码如下:

private void start(boolean playBackwards) {
        .....
        mReversing = playBackwards;
        ...
        // 变量重置初始化
        mStarted = true;
        mPaused = false;
        mRunning = 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 = 0;
        // 重点关注此处 AnimationHandler
        AnimationHandler animationHandler = AnimationHandler.getInstance();
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

        if (mStartDelay == 0 || mSeekFraction >= 0) {
            // 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);
            }
        }
    }

在 startAnimation() 中,做了一些变量的初始化工作,重点关注该方法中的 AnimationHandler 和 startAnimation() 具体做了什么,首先看startAnimation() 代码如下:

/**
     * Called internally to start an animation by adding it to the active animations list. Must be
     * called on the UI thread.
     */
    private void startAnimation() {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                    System.identityHashCode(this));
        }

        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }

  可以看到,在startAnimation() 中,仅仅只是进行了动画的初始化工作,并且通知 listener 去回调 onAnimationStart() 方法,那么真正的动画执行时机是什么时候呢,回到刚才的 AnimationHandler ,其addAnimationFrameCallback() 代码如下:

    /**
     * Register to get a callback on the next frame after the delay.
     */
    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));
        }
    }

   上述方法比较简单,首先看下getProvider()实例如下代码:

  /**
     * Default provider of timing pulse that uses Choreographer for frame callbacks.
     */
    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(编舞者) 实例,它负责提供垂直同步信号(每次间隔16.67ms),用来进行屏幕刷新,如果对 Choreographer 有兴趣可以参考Android Choreographer 源码分析这篇文章。postFrameCallback() 方法向 Choreographer 注册了一份 callback , 在 Choreographer 接收到垂直同步信号后,便会对该 callback 实例进行回调。下来看看这个 callback 是什么,代码如下:

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

   callback最终会回调到 doFrame() 方法,在该方法中,通过 doAnimationFrame() 真正执行更新逻辑,如果动画没有结束,那么将该callBack 重新进行注册,接收后续的垂直同步信息。doAnimationFrame() 代码如下:

/**
     * Processes a frame of the animation, adjusting the start time if needed.
     *
     * @param frameTime The frame time.
     * @return true if the animation has ended.
     * @hide
     */
    public final void doAnimationFrame(long frameTime) {
        AnimationHandler handler = AnimationHandler.getInstance();
        ....
        //
        boolean finished = animateBasedOnTime(currentTime);

        if (finished) {
            endAnimation();
        }
    }

   可以看到,动画的主要逻辑都在 animateBasedOnTime() 里面, 并且根据返回值来判断是否要进行结束的回调,下面看一下该方法具体做了什么,代码如下:

/**
     * This internal function processes a single animation frame for a given animation. The
     * currentTime parameter is the timing pulse sent by the handler, used to calculate the
     * elapsed duration, and therefore
     * the elapsed fraction, of the animation. The return value indicates whether the animation
     * should be ended (which happens when the elapsed time of the animation exceeds the
     * animation's duration, including the repeatCount).
     *
     * @param currentTime The current time, as tracked by the static timing handler
     * @return true if the animation's duration, including any repetitions due to
     * <code>repeatCount</code> has been exceeded and the animation should be ended.
     */
    boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            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);
            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;
            }
            mOverallFraction = clampFraction(fraction);
            float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
            animateValue(currentIterationFraction);
        }
        return done;
    }

  方法的前半部分主要进行当前fraction进度的修正,以及是否要执行onAnimationRepeat() 的回调,真正刷新继续往下找,animateValue()方法如下:

 void animateValue(float fraction) {
        // 根据插值器类型的不同,计算当前fraction 对应的值..
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            // 将 fraction 转换成真正的 animatedValue .. 
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                // 最熟悉的 onAnimationUpdate() 回调,可以在该回调做
                // 任何自己想要做的操作, 根据上面算出的 animatedValue
                // 去更新自己想要的属性。
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

  从代码可以看出,首先通过插值器去修正 fraction 的值,插值器类型可以通过 setInterpolator() 来指定,得到 fraction 以后,再将当前 fraction 转换成真正的 animatedValue ,最后通过 listener完成onAnimationUpdate() 回调即可。
  自此, Animator 大体运作原理分析完毕,Animator 的核心逻辑还是依靠 Choreographer 来完成更新,并且需要注意一点的是, 因为 AnimationHandler 和 Choregrapher 是 ThreadLocal 线程私有的,每一个线程如果需要都会有各自的 Choregrapher 实例,所以 ValueAnimator 是完全可以运行在子线程中的,只要在 ValueAnimator 运行中不涉及到 UI 的更新即可。这一点 ValueAnimator 注释也已经很清楚了,说明如下:

/**
     * Start the animation playing. This version of start() takes a boolean flag that indicates
     * whether the animation should play in reverse. The flag is usually false, but may be set
     * to true if called from the reverse() method.
     *
     * <p>The animation started by calling this method will be run on the thread that called
     * this method. This thread should have a Looper on it (a runtime exception will be thrown if
     * this is not the case). Also, if the animation will animate
     * properties of objects in the view hierarchy, then the calling thread should be the UI
     * thread for that view hierarchy.</p>
     *
     * @param playBackwards Whether the ValueAnimator should start playing in reverse.
     */

  依靠着 Choreographer ,在理想情况下,ValueAnimator 每间隔 16.67ms 会重新计算 animatorValue , 以此来更新界面。在实际情况中,可以用 Choreographer 做很多事情,比如应用的帧率检测等等,在此推荐一个优秀的帧率检测开源项目。其核心也是通过 choreographer 来实现。
  戳我 >> TinyDancer

   参考文章:
  View 动画 Animation 运行原理解析
  Android Choreographer 源码分析
  Android Choreographer 源码分析

相关文章

网友评论

      本文标题:Animator

      本文链接:https://www.haomeiwen.com/subject/omhssftx.html