Synopsis
前面在 Android 动画基础中已经简单讲了 View 动画的基本用法,本文将通过一个简单的例子来介绍 View 动画的内部原理。
Sample
val alphaAnim = AlphaAnimation(0f, 1f)
alphaAnim.duration = 3000
button.startAnimation(alphaAnim)
Let’s make it brief
我们先直接从流程图上看从 startAnimation 开始做了什么:
 它会调用 View 的 invalidate() 方法来进行刷新,并将该 View 的大小和位置传给 invalidateInternal() 方法,然后调用 parent 的 invalidateChild(),该方法会循环调用 parent.invalidateChildInParent 直到 parent 为空。首次 parent 为自身,返回值为自己的 parent。我们再来看下这个方法的流程图:
它会调用 View 的 invalidate() 方法来进行刷新,并将该 View 的大小和位置传给 invalidateInternal() 方法,然后调用 parent 的 invalidateChild(),该方法会循环调用 parent.invalidateChildInParent 直到 parent 为空。首次 parent 为自身,返回值为自己的 parent。我们再来看下这个方法的流程图:
 我们印象中 View 的 parent 一定是 ViewGroup,毕竟顶级 View 为 DecorView,它继承自 FrameLayout。那么 parent.invalidateChildInParent 就会一直调用的是 ViewGroup 中的该方法。而该方法主要是根据传过来的 View 的大小和位置以及自己的大小来计算出所需要绘制的区域,一直回溯到最上层得到最终所需要绘制的区域。 里面没有任何跟动画相关的操作,那么它是如何触发的呢,其实 DecorView 是会被添加到 ViewRootImpl 中,最终执行的是它的方法。它内部会将一个包含 performTraversals() 方法的 Runnable 传给 Choreographer,等到系统发出刷新信号后就会执行。该方法主要是遍历 View 树来执行 onMeasure 等方法
我们印象中 View 的 parent 一定是 ViewGroup,毕竟顶级 View 为 DecorView,它继承自 FrameLayout。那么 parent.invalidateChildInParent 就会一直调用的是 ViewGroup 中的该方法。而该方法主要是根据传过来的 View 的大小和位置以及自己的大小来计算出所需要绘制的区域,一直回溯到最上层得到最终所需要绘制的区域。 里面没有任何跟动画相关的操作,那么它是如何触发的呢,其实 DecorView 是会被添加到 ViewRootImpl 中,最终执行的是它的方法。它内部会将一个包含 performTraversals() 方法的 Runnable 传给 Choreographer,等到系统发出刷新信号后就会执行。该方法主要是遍历 View 树来执行 onMeasure 等方法
View.startAnimation()
首先我们来看下 View 的 startAnimation 到底做了什么:
public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    invalidateParentCaches();
    invalidate(true);
}
我们来看下 setAnimation(animation) 方法:
public void setAnimation(Animation animation) {
    mCurrentAnimation = animation;
    if (animation != null) {
        // If the screen is off assume the animation start time is now instead of
        // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
        // would cause the animation to start when the screen turns back on
        if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
                && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
            animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
        }
        animation.reset();
    }
}
这里是将我们创建的 animation 对象赋值给 View 的 Animation 对象,并没有具体的动画逻辑,我们再来看下 invalidateParentCaches():
/**
 * Used to indicate that the parent of this view should clear its caches. This functionality
 * is used to force the parent to rebuild its display list (when hardware-accelerated),
 * which is necessary when various parent-managed properties of the view change, such as
 * alpha, translationX/Y, scrollX/Y, scaleX/Y, and rotation/X/Y. This method only
 * clears the parent caches and does not causes an invalidate event.
 *
 * @hide
 */
protected void invalidateParentCaches() {
     if (mParent instanceof View) {
         ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
      }
}
从注释可以看出,这个方法是给父 View 增加一个标志位,用于告诉它要清理自己的缓存。当多个由父 View 管理的该子 View 的属性(如 alpha, translationX/Y 等)变化时会用到。我们再往下看:
    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }
    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
            // ...
            if (skipInvalidate()) { // 当 View 不为 VISIBLE 且没有动画时,直接不绘制
                return;
            }
            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                // 调用 parent 的 invalidateChild 来刷新
                p.invalidateChild(this, damage);
            }
            // ...
可以看出它调用了 ViewGroup 的 invalidateChild()方法:
    public final void invalidateChild(View child, final Rect dirty) {
        // ...
        ViewParent parent = this;
        if (attachInfo != null) {
            // 如果子 View 有动画,则要将该标志位复制给自己和自己的 parent 以确保刷新请求能通过
            final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
            // ...
            do {
                View view = null;
                if (parent instanceof View) { // 从这里看出在 View 树中,某些 parent 可能不为 View
                    view = (View) parent;
                }
                if (drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) { 
                        // 看出 View 的某个 parent 为 ViewRootImpl(不是 ViewGroup)
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }
                // ...
                // 循环调用该方法,直到 parent 为 null。循环开始时,parent 为自身。
                // location 为 child 左上角与自己的左上角距离
                // dirty 为所要绘制的区域[0, 0, width, height]
                parent = parent.invalidateChildInParent(location, dirty);
                // invalidateChildInParent 执行完后会得到新的 dirty 和 location
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix(); // 获得当前的 matrix
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        // boundingRect 即为当前所绘制的区域大小,即[0, 0, width, height]
                        boundingRect.set(dirty); 
                        // 如果该 view 有做 matrix 变换,如 matrix.setScale(2f, 1f)
                        // 即将宽度变为2倍,那么要将 boundingRect 变为[0, 0, 2 * widht, height]
                        m.mapRect(boundingRect);
                        // 然后将真实所应绘制的区域设置给 dirty
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }
可以看出它会获得当前 view 的绘制区域dirty:[0, 0, width, height],以及该 view 相对父 view 的距离 location:[left, top],里面循环调用 invalidateChildInParent(location, dirty)。我们进去看看:
    /**
     * This implementation returns null if this ViewGroup does not have a parent,
     * if this ViewGroup is already fully invalidated or if the dirty rectangle
     * does not intersect with this ViewGroup's bounds.
     */
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
            // either DRAWN, or DRAWING_CACHE_VALID
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
                    != FLAG_OPTIMIZE_INVALIDATE) {
                // 用 dirty 和 location计算出绘制区域相对于 parent 的坐标 
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
                // 不裁减(即子 view 和 parent 有重叠的部分也需要进行绘制)则取并集得到新的绘制区域
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }
                final int left = mLeft;
                final int top = mTop;
                // 裁减的话,则取交集(仅仅绘制子 View 内有效的区间)
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                        dirty.setEmpty();
                    }
                }
                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;
            } else {
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    // in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }
                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;
                mPrivateFlags &= ~PFLAG_DRAWN;
            }
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }
            return mParent;
        }
        return null;
    }
可以看出它所做的事情就是通过是否要进行裁减来决定绘制区域,这个函数是返回当前 ViewGroup 的 parent 的。里面还没有看到具体的动画逻辑,由于 invalidateChildInParent 会一直循环执行,一直到 parent 为 null 为止,而 ViewGroup 的 parent 也是 ViewGroup,那么无论循环多少次都是调用一样的方法,都没有跟具体的动画逻辑有关系,那么动画到底是在哪里触发的呢?  
我们知道,我们的布局是通过 setContentView() 时添加到 DecorView 中的,那么 DecorView 就是 View 树的根布局,而 DecorView 其实是会在 WindowManagerGlobal 的 addView 方法被添加到 ViewRootImpl 中,这个才是真正的根布局(虽然并没有继承 ViewGroup)。我们来看下 ViewRootImpl 的 invalidateChildInParent 方法:
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        // ...
        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }
        // ...
        invalidateRectOnScreen(dirty);
        return null;
    }
从 ViewGroup 的 invalidateChildInParent 方法里看出来 dirty 是不为空的,那么会直接走到 invalidateRectOnScreen 方法中:
    private void invalidateRectOnScreen(Rect dirty) {
        // ...
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            scheduleTraversals();
        }
    }
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            // ...
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            // ...
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    void doTraversal() {
        if (mTraversalScheduled) {
            // ...
            performTraversals();
            // ...
        }
    }
可以看出它的目的是将 performTraversals() 方法封装到 Runnalbe 里,然后传给 Choreographer。我们来看下传给它后做了什么:
    /**
     * Posts a callback to run on the next frame.
     * <p>
     * The callback runs once then is automatically removed.
     * </p>
     *
     * @param callbackType The callback type.
     * @param action The callback action to run during the next frame.
     * @param token The callback token, or null if none.
     *
     * @see #removeCallbacks
     * @hide
     */
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }
从注释可以看出,这个将 Runnable 传进来是为了在屏幕绘制下一帧的时候去执行。也就是我们调用 start 启动动画后并不是直接执行的,而是在屏幕绘制下一帧的时候才会执行。这个方法最终会调用到:
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            
            // 会将 Runnable 封装到 CallbackRecord 中,然后存入队列中
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }
Runnable 被存入到 Choreographer 的待执行队列中,当屏幕绘制信号(安卓手机屏幕刷新频率为60fps,也就是16.7ms屏幕会刷新一次)到来时会被执行,这样 ViewRootImpl 的 performTranversals() 就会执行。这个方法是从 DecorView 开始遍历 View 树来执行 onMeasure 等操作的。由于每一次页面刷新都会执行 ViewRootImpl 的 performTraversal() 操作来遍历 View 树,找到发生改变的 View 来执行 onMeasure 等操作,所以我们写布局时要尽量减少层级。
我们来总结下调用 startAnimation 后的流程: 可以回顾下前面的流程图
- 调用 View.StartAnimation(Animation) 方法后,会调用 setAnimation() 将这个 animation 会作为 View 的全局变量保存起来
- 然后调用 View 的 invalidate() 方法,它会调用 parent.invalidateChild() 方法来调到 ViewGroup 中的该方法。
- 这时 ViewGroup 会循环执行parent = parent.invalidateChildInParent(location, dirty)直到 parent 为空。
整个过程就处理完了,但 ViewGroup 中的 invalidateChildInParent 都是在处理一些参数,并没有和动画有直接联系,那动画到底是在哪里处理的?想到整个 View 树的 parent 不仅仅是 ViewGroup,它的顶层是 ViewRootImpl。ViewRootImpl 中的 invalidateChildInParent() 才是核心所在。它会将一个会执行 performTraversals() 的 Runnable 传到 Choreographer,当屏幕绘制下一帧的时候就会执行。真正跟动画相关的代码在 performTraversals() 中。该操作会执行测量,布局和绘制。由于动画相关代码在 draw() 里,所以我们直接进入 View 的 draw() 方法:
    public void draw(Canvas canvas) {
        // ...
        // Step 1, draw the background, if needed
        int saveCount;
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
        // 如果可以的话,跳过第2和5步
        // ...
        if (!verticalEdges && !horizontalEdges) {
            // Step 3,开始调用 onDraw() 方法来绘制内容
            if (!dirtyOpaque) onDraw(canvas);
            // Step 4, 绘制子 View。这个方法的具体实现在 ViewGroup 中
            dispatchDraw(canvas);
            drawAutofilledHighlight(canvas);
            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }
            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);
            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);
            if (debugDraw()) {
                debugDrawFocus(canvas);
            }
            // we're done...
            return;
        }
        // 后面就将 2~6 步骤完整走一遍
    }
draw() 方法做了6步:
- 绘制背景
- 如果有必要的话,保存 canvas 图层来为 fading 做准备
- 调用 onDraw(canvas) 绘制 View 的内容
- dispatchDraw(canvas) 绘制子 View,这一步具体是由 ViewGroup 实现
- 如果有必要的话,绘制 fading 边界并保存图层
- 绘制装饰(如 scrollbars)
第四步,它会调用 child.draw(Canvas canvas, View child, long drawingTime) 方法来触发子 View 的 draw(Canvas, View, long) 方法。 我们来看下这个方法:
    /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        // ...
        boolean more = false;
        // ...
        // 获取在启动动画时传进来的 Animation 对象
        final Animation a = getAnimation();
        if (a != null) {
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
            // ...
        } 
        // ...
        return more;
    }
可以看到这个方法中获取了之前传给 View 的 animation,然后传给 applyLegacyAnimation 方法:
    /**
     * Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
     * case of an active Animation being run on the view.
     */
    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        Transformation invalidationTransform;
        final int flags = parent.mGroupFlags;
        final boolean initialized = a.isInitialized();
        if (!initialized) {
            // 初始化动画
            a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
            a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
            if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
            onAnimationStart();
        }
        final Transformation t = parent.getChildTransformation();
        // 获取动画的 Transformation, 返回值标志动画有没有做完,true 代表没有完成
        boolean more = a.getTransformation(drawingTime, t, 1f);
        // ...
        // 动画未执行完
        if (more) {
            if (!a.willChangeBounds()) { // 动画是否会改变大小,alpha 动画会返回 false, 其余动画返回 true
                if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
                        ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
                    parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
                } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
                    // The child need to draw an animation, potentially offscreen, so
                    // make sure we do not cancel invalidate requests
                    parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    parent.invalidate(mLeft, mTop, mRight, mBottom);
                }
            } else {
                if (parent.mInvalidateRegion == null) {
                    parent.mInvalidateRegion = new RectF();
                }
                final RectF region = parent.mInvalidateRegion;
                a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                        invalidationTransform);
                // The child need to draw an animation, potentially offscreen, so
                // make sure we do not cancel invalidate requests
                parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                final int left = mLeft + (int) region.left;
                final int top = mTop + (int) region.top;
                // 内部调用 invalidateInternal 方法,然后将前面讲过的流程再走一遍,一直循环到动画结束 
                parent.invalidate(left, top, left + (int) (region.width() + .5f),
                        top + (int) (region.height() + .5f));
            }
        }
        return more;
    }
可以看出这里真正开始初始化,处理动画了。我们来看下 getTransformation 方法:
    /**
     * Gets the transformation to apply at a specified point in time. Implementations of this
     * method should always replace the specified Transformation or document they are doing
     * otherwise.
     *
     * @param currentTime Where we are in the animation. This is wall clock time.
     * @param outTransformation A transformation object that is provided by the
     *        caller and will be filled in by the animation.
     * @return True if the animation is still running
     */
    public boolean getTransformation(long currentTime, Transformation outTransformation) {
        if (mStartTime == -1) { // 记录动画开始执行的时间
            mStartTime = currentTime;
        }
        final long startOffset = getStartOffset(); // 默认为0
        final long duration = mDuration;
        float normalizedTime;
        if (duration != 0) {
            // 根据当前时间计算动画的进度的百分比
            normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                    (float) duration;
        } else {
            // time is a step-change with a zero duration
            normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
        }
        final boolean expired = normalizedTime >= 1.0f || isCanceled();
        // mMore 用来判断后面还有没有动画要执行。true 代表当前动画已经执行完了。
        mMore = !expired;
        if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
            if (!mStarted) { // 动画还没有 start()
                fireAnimationStart();
                mStarted = true;
                if (NoImagePreloadHolder.USE_CLOSEGUARD) {
                    guard.open("cancel or detach or getTransformation");
                }
            }
            if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
            if (mCycleFlip) { 
                normalizedTime = 1.0f - normalizedTime;
            }
            // 将当前动画的进度传给 interpolator,获取实际的动画进度
            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
            // 执行动画
            applyTransformation(interpolatedTime, outTransformation);
        }
        // 动画执行完了
        if (expired) {
            if (mRepeatCount == mRepeated || isCanceled()) { // 如果动画被取消或者动画重复也做完了
                if (!mEnded) {
                    mEnded = true;
                    guard.close();
                    fireAnimationEnd();
                }
            } else { // 动画需要重复播放
                if (mRepeatCount > 0) {
                    mRepeated++;
                }
                if (mRepeatMode == REVERSE) { // 动画反转
                    mCycleFlip = !mCycleFlip;
                }
                mStartTime = -1;
                mMore = true;
                fireAnimationRepeat();
            }
        }
        if (!mMore && mOneMoreTime) {
            mOneMoreTime = false;
            return true;
        }
        return mMore;
    }
可以看出逻辑比较简单,就是计算当前动画的进度,然后将进度控制在0-1之间,将动画进度传给插值器获得动画的真实进度,然后执行。  
它的返回值是用来判断动画是否已经执行完,如果没有执行完成的话,applyLegacyAnimation() 中会调用 parent.invalidate() 方法,它会调用 View.invalidateInternal() 方法,层层通知到 ViewRootImpl 然后再次发起一次遍历请求,等下一帧信号来的时候,就会执行 performTranversal() 遍历 View 树,调用 draw() 方法,然后调用到 applyLegacyAnimation() 来执行动画操作,这些就是前面讲过的。
conclusion
- 调用 startAnimation() 方法将 Animation 传给 View,然后调用 View 的 invalidate() 方法一层层的往上通知到 ViewRootImpl,它会将布局绘制操作封装到一个 Runnable 然后传给 Choreographer,等受到处置信号时,就会执行这个 Runnable 来绘制下一帧。
- 这个布局绘制操作会从 DecorView 开始往下遍历 View 树,并调用 draw() 方法,然后会调用 applyLegacyAnimation() 方法来处理动画。
- applyLegacyAnimation() 方法会调用 Animation 类的 getTransformation() 方法,它是用来计算当前动画的进度并使用 applyTransformaion() 方法来执行动画。getTransformation() 的返回值标志动画是否完成,没有完成的话,会再次调用“invalidate”方法,重复上面步骤1,等下次受到垂直信号时,再次执行动画,直到动画结束。
