Android Animation 执行原理

在 Android 的 Animation 支持下,衍生出了 ScaleAnimation / AphlaAnimation / 3D Animation …… 及各种自定义 Animation 。Animation 优秀的设计简化和方便了动画的定制(当然也有一些不通用的代码)。

Animation 都是在 View 层就完成的,所以代码原理比较易懂。以下代码分析是建立在 AOSP 6.0 的源码上。

一、调用链路

关键链路 api :在 View 执行 Animation 时候需要的 api

// 初始化 Animation 中的 Transformation (变化)
void initializeInvalidateRegion(int left, int top, int right, int bottom);
// 获取 Animation 执行后应该重绘的区域
void getInvalidateRegion(int left, int top, int right, int bottom, RectF invalidate, Transformation transformation);
// 根据当前时间获取 Animation 中的 Transformation (变化)
boolean getTransformation(long currentTime, Transformation outTransformation,  float scale);
复制代码

调用路径

Animation 的绘制是跟随 View 的绘制周期。关键调用链路如下(中间省略大部分):

ViewRootImpl.performDraw() → ViewGroup.drawChild() → View.draw()

来到 View 的 draw 方法,与 Animation 相关的关键思路:

  1. Vsync 周期,调用 View draw 方法,当前 View Animation 不为空;
  2. Animation 未初始化,通过 initialize() 方法重置内部属性,initializeInvalidateRegion() 方法,初始化 Animation 第一帧(受 FillBefore 影响),继而发起 onAnimationStart() 通知;
  3. 根据当前时间通过 Animation.getTransformation() 方法,获取属性在时间轴上的变化和 Animation 是否结束。
  4. Animation 还需要继续进行,根据 Animation 是否会引起绘制区域改变,请求父亲相应重绘 api,以便下一帧 Animation 能继续进行。
  5. 当 Animation 会改变矩阵,则将矩阵应用到 Canvas 或者是 RenderNode。
  6. 后续正常 draw 绘制流程,包括裁剪可用区域,背景、内容、前景。

关键代码和注释 View.java

// 启动 Animation
public void startAnimation(Animation animation) {
    // 设置开始时间为下一帧
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    // 触发重绘
    invalidateParentCaches();
    invalidate(true);
}
// 设置 Animation
public void setAnimation(Animation animation) {
    mCurrentAnimation = animation;

    if (animation != null) {
        // 在屏幕是暗的情况下,如果 Animation 设置了下一帧启动,由于不会触发重绘,所以需要设置
        // 启动时间为当前时间,否则 Animation 的启动时间会等到屏幕亮起后被设置
        if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
            && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
            animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
        }
        animation.reset();
    }
}

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    
    /* 省略... */

    boolean more = false;
    final boolean childHasIdentityMatrix = hasIdentityMatrix();
    
    /* 省略... */

    Transformation transformToApply = null;
    boolean concatMatrix = false;
    final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
    // 获取 Animation
    final Animation a = getAnimation();
    if (a != null) {
        // 进行余下的 Animation
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        concatMatrix = a.willChangeTransformationMatrix();
        if (concatMatrix) {
            mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
        }
        // parent.getChildTransformation() 获取的矩阵就是 applyLegacyAnimation() 方法时
        // 操作的矩阵。(这个写法真的很奇怪,因为 transformation 本身就可以直接跟着当前 view,代码中也
        // 没有其他和父亲耦合的逻辑)
        transformToApply = parent.getChildTransformation();
    } else {
        
        /* 省略... */
        
    if (transformToApply != null
        /* 省略... */
            if (transformToApply != null || !childHasIdentityMatrix) {
                /* 省略... */
                if (transformToApply != null) {
                    if (concatMatrix) {
                        if (drawingWithRenderNode) {
                            // 如果使用的是硬件绘制,将 Transform 变化结果应用到 renderNode
                         	renderNode
                             .setAnimationMatrix(transformToApply.getMatrix());
                        } else {
                            // 如果使用的是软件绘制,将 Transform 变化结果应用到 Canvas
                            canvas.translate(-transX, -transY);
                            canvas.concat(transformToApply.getMatrix());
                            canvas.translate(transX, transY);
                        }
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }
	/* 省略... */
}
                
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) {
        // 初始化 Animation
        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);
        // 触发 View onAnimationStart 回调
        onAnimationStart();
    }
	
    final Transformation t = parent.getChildTransformation();
    // 获取 Animation 对于矩阵的变化,more 代表 Animation 未结束
    boolean more = a.getTransformation(drawingTime, t, 1f);
    /* 省略... */
    
    if (more) {
        if (!a.willChangeBounds()) {
            // 当 Animation 不会涉及到本身视图大小的改变时,重绘 view 本身大小区域
            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 {
             // 当 Animation 涉及到本身视图大小的改变时,重绘 Animation 给出的重绘区域
            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;
            parent.invalidate(left, top, left + (int) (region.width() + .5f),
                              top + (int) (region.height() + .5f));
        }
    }
    return more;
}

复制代码

二、内部实现细节

有一些不为人知的逻辑,必须要通过了解内部实现!下面涉及的代码均在 Animation.java。

关键属性

// 如果当 FillEnabled = true, FillBefore 的设置才能生效
void setFillEnabled(boolean fillEnabled);
// 在动画结束之后保持最后一帧
void setFillAfter(boolean fillAfter);
// 在动画开始之前将第一帧应用在 View 上
void setFillBefore(boolean fillBefore);
// 设置动画开始时的延时,每一次循环都会延时
void setStartOffset(long startOffset);
// 设置重复模式,支持 RESTART 重新开始 / REVERSE 不断反转
void setRepeatMode(int repeatMode);
// 设置重复次数
void setRepeatCount(int repeatCount);
// 设置差值器,决定动画在时间轴的变化率
void setInterpolator(Interpolator i);
复制代码

内部实现原理

View 给出的时间和开始时间和延时时间进行相减,在与设置的时长进行归一化,将归一化时间交给差值器处理,获得变化率,再交给子类的 applyTransformation 去根据这个变化率对矩阵等属性进行任意的变化。

关键 api

// 该方法是每次帧重绘都会调用
public boolean getTransformation(long currentTime, Transformation outTransformation) {
    // 设置下一帧开始,其实就是等到真正帧开始时,才设置启动时间。
    if (mStartTime == -1) {
        mStartTime = currentTime;
    }

    final long startOffset = getStartOffset();
    final long duration = mDuration;
    float normalizedTime;
    if (duration != 0) {
        // 根据当前时间、开始时间、延时时间和总时长计算归一化时间。
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
            (float) duration;
    } else {
        // 瞬时变化
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
    }
	// 判断动画是否已经结束
    final boolean expired = normalizedTime >= 1.0f;
    mMore = !expired;
	// 如果没有设置 FillEnable,归一化时间一定会在[0,1]的范围,也就是如果 FillBefore 设置成 false了
    // 仍然会提前绘制第一帧
    if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
	// 当归一化时间在[0, 1],或者 FillBefore = true 或者 FillAfter = true,进行动画内容填充
    if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
        if (!mStarted) {
            fireAnimationStart();
            mStarted = true;
            if (USE_CLOSEGUARD) {
                guard.open("cancel or detach or getTransformation");
            }
        }
		// 将时间限制在[0,1]
        if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
		// 如果是周期反转,则将归一化时间反转
        if (mCycleFlip) {
            normalizedTime = 1.0f - normalizedTime;
        }
		// 将归一化时间交给差值器,得到变化率
        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
        // 将变化率交给真正涉及应用属性变化的操作
        applyTransformation(interpolatedTime, outTransformation);
    }

    if (expired) {
        if (mRepeatCount == mRepeated) {
			// 动画结束
            if (!mEnded) {
                mEnded = true;
                guard.close();
                fireAnimationEnd();
            }
        } else {
            if (mRepeatCount > 0) {
                mRepeated++;
            }
			// 设置周期反转
            if (mRepeatMode == REVERSE) {
                mCycleFlip = !mCycleFlip;
            }
			// 重新开始新的周期,重设开始时间
            mStartTime = -1;
            mMore = true;

            fireAnimationRepeat();
        }
    }
	// OneMoreTime 用于绘制最后一帧,也就是 mPreviousTransformation。具体原因请看 
    // initializeInvalidateRegion() 方法
    if (!mMore && mOneMoreTime) {
        mOneMoreTime = false;
        return true;
    }

    return mMore;
}

// 真正根据变化率进行属性变化的 api
protected void applyTransformation(float interpolatedTime, Transformation t) {
}

// 根据给定的区域进行初始化,名字是这样,但真正做的,也就保存了一下更新区域,在之后,有对象来获取
// 更新区域时再使用。
public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
    final RectF region = mPreviousRegion;
    region.set(left, top, right, bottom);
    // Enlarge the invalidate region to account for rounding errors
    region.inset(-1.0f, -1.0f);
    if (mFillBefore) {
        // 如果设置了 FillBefore,那么就提前进行首帧动画的应用,而不通过 getTransformation()方法
        // mPreviousTransformation 在这里很关键,原因请看 getInvalidateRegion()
        final Transformation previousTransformation = mPreviousTransformation;
        applyTransformation(mInterpolator.getInterpolation(0.0f), previousTransformation);
    }
}

// 获取动画改变后需要重绘的区域
public void getInvalidateRegion(int left, int top, int right, int bottom,
                                RectF invalidate, Transformation transformation) {
	
    final RectF tempRegion = mRegion;
    final RectF previousRegion = mPreviousRegion;

    invalidate.set(left, top, right, bottom);
    // 将矩阵的变化应用到当前矩阵,确定矩阵对于大小的影响
    transformation.getMatrix().mapRect(invalidate);
    // Enlarge the invalidate region to account for rounding errors
    invalidate.inset(-1.0f, -1.0f);
    tempRegion.set(invalidate);
    // 将矩阵所需大小与之前保存的更新区域取并集
    invalidate.union(previousRegion);

    previousRegion.set(tempRegion);
	// mTransformation 只是一个 temp 辅助交换逻辑
    final Transformation tempTransformation = mTransformation;
    final Transformation previousTransformation = mPreviousTransformation;
	// 交换 mPreviousTransformation 和当前 transformation. 这个 api 是 
    // view.applyLegacyAnimation() 方法时获取 Animation 变化的时机,即获取后马上就绘制的。而这里是
    // 把前一帧交换出去绘制,留下了当前帧,所以上述的 mOneMoreTime 的来由就在这里,onMoreTime 把余下的
    // 一帧展示。
    tempTransformation.set(transformation);
    transformation.set(previousTransformation);
    previousTransformation.set(tempTransformation);
}
复制代码

到这里,整个 Animation 的关键代码已经分析结束了,类少易懂,作用大。里面的设计可以说很好了,但是仍然有些差强人意的地方,就像是 mPreviousAnimation 和 mOneMoreTime(如果不真正咬文嚼字,重写的时候就可能会遇到各种坑),其实不需要这样做也能把动画的帧完全显示的。

三、设计模式分析

Animation 使用了模板方法的设计模式,定义了一个基类和关键动画链路,由实现的子类去完成关键的动画方法,让整个动画变得易于拓展,隐蔽实现细节。

Intepolator 差值器使用了策略模式,将差值器的算法交给实现类完成,让动画在时间抽上的效果得以丰富多样化。比如衍生出了 LinearIntepolator / AccelerateInterpolator / PathInterpolatorCompatBase(cubic-bezier)...

四、继承类分析

AplhaAnimation

AplhaAnimation 继承自 Animation,进行透明度动画,关键属性:mFromAlpha / mToAlpha。通过变化率改变 Transformation 中的 alpha 值。

// 重写父类 Animation 方法
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    final float alpha = mFromAlpha;
    // 根据变化率求出当前 alpha 值,在设置给 Transformation。
    t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
复制代码

Transformation 中的 alpha 怎么生效的?跟进 draw 方法里看看!主要调用到的方法是,当硬件加速时,使用的是 RenderNode.setAlpha();当软件加速时,是 canvas.saveLayoutAplha()方法,以及 paint.setAlpha() 方法。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    /* 省略... */
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
    /* 省略... */
    
    float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
    if (transformToApply != null
        || alpha < 1
        || !hasIdentityMatrix()
        || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
        if (transformToApply != null || !childHasIdentityMatrix) {
    		/* 省略... */

            if (transformToApply != null) {
                if (concatMatrix) {
                    // 应用动画矩阵到 canvas 或者 RenderNode
                    if (drawingWithRenderNode) {
                        renderNode.setAnimationMatrix(transformToApply.getMatrix());
                    } else {
                        canvas.translate(-transX, -transY);
                        canvas.concat(transformToApply.getMatrix());
                        canvas.translate(transX, transY);
                    }
                    parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                }
				// 获取在动画 Transformation 中设置的 alpha 值
                float transformAlpha = transformToApply.getAlpha();
                if (transformAlpha < 1) {
                    alpha *= transformAlpha;
                    parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                }
            }
    		/* 省略... */
        }

        // 当 alpha 需要设置时
        if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
    		/* 省略... */
            if (!drawingWithDrawingCache) {
                final int multipliedAlpha = (int) (255 * alpha);
                if (!onSetAlpha(multipliedAlpha)) {
                    if (drawingWithRenderNode) {
                        // 如果是硬件加速,则通过 RenderNode.setAlpha 方法设置透明度
                        renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
                    } else if (layerType == LAYER_TYPE_NONE) {
                        // 如果是软件绘制,则通过 Canvas.saveLayerApla 方法时启用画布新层时
                        // 设置透明度。
                        canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
                                              multipliedAlpha);
                    }
    /* 省略... */
    if (!drawingWithDrawingCache) {
    	/* 省略... */
    } else if (cache != null) {
		// 当需要绘制缓存的内容时
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        if (layerType == LAYER_TYPE_NONE) {
    		/* 省略... */
            // 透明度设置到画笔上
            cachePaint.setAlpha((int) (alpha * 255));
            canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
        } else {
            // 合并layerPaint 本身设置的透明度和将要设置的透明度,设置到画笔上
            int layerPaintAlpha = mLayerPaint.getAlpha();
            mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
            canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
            mLayerPaint.setAlpha(layerPaintAlpha);
        }
    }
    /* 省略... */
}
复制代码

ScaleAnimation

ScaleAnimation 继承自 Animation ,缩放动画的实现。关键通过 Matrix.setScale 完成。

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float sx = 1.0f;
    float sy = 1.0f;
    float scale = getScaleFactor();
	// 根据变化率算出当前应该设置的缩放值
    if (mFromX != 1.0f || mToX != 1.0f) {
        sx = mFromX + ((mToX - mFromX) * interpolatedTime);
    }
    if (mFromY != 1.0f || mToY != 1.0f) {
        sy = mFromY + ((mToY - mFromY) * interpolatedTime);
    }
	// 最后设置到矩阵上
    if (mPivotX == 0 && mPivotY == 0) {
        t.getMatrix().setScale(sx, sy);
    } else {
        // 同时设置中心点
        t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
    }
}
复制代码

AnimationSet

AnimationSet 继承自 Animation,组合动画的实现。通过 AnimationSet 可以实现多个同时执行,利用 StartOffset 也能实现串行,当然也有局限,比如没法设置循环、没有整体的 repeatMode(自己倒腾一下就可以,比如捣腾下 normalizedTime)。

实现细节:遍历孩子的 getTransformation() 方法,将 Transform 结果合成。

注意:(具体可以看代码)

  1. duration、 repeatMode、 fillBefore、 fillAfter 这些属性是直接设置给子 Animation;
  2. repeatCount、fillEnabled 这些属性不支持;
  3. startOffset、 shareInterpolator只能应用在 AnimationSet 本身;
  4. sharInterpolator 起效时候,子 Animation 的 sharInterpolator 失效;
public void initialize(int width, int height, int parentWidth, int parentHeight) {
    /* 省略 */
    // 这里就可以看出哪些是被设置进孩子的
    for (int i = 0; i < count; i++) {
        Animation a = children.get(i);
        if (durationSet) {
            a.setDuration(duration);
        }
        if (fillAfterSet) {
            a.setFillAfter(fillAfter);
        }
        if (fillBeforeSet) {
            a.setFillBefore(fillBefore);
        }
        if (repeatModeSet) {
            a.setRepeatMode(repeatMode);
        }
        if (shareInterpolator) {
            a.setInterpolator(interpolator);
        }
        if (startOffsetSet) {
            long offset = a.getStartOffset();
            // 孩子在 AnimationSet 的 startOffset 上加上了自身的 startOffset
            a.setStartOffset(offset + startOffset);
            storedOffsets[i] = offset;
        }
        a.initialize(width, height, parentWidth, parentHeight);
    }
}

public boolean getTransformation(long currentTime, Transformation t) {
    final int count = mAnimations.size();
    final ArrayList animations = mAnimations;
    final Transformation temp = mTempTransformation;

    boolean more = false;
    boolean started = false;
    boolean ended = true;

    t.clear();
	// 遍历子 Animation 
    for (int i = count - 1; i >= 0; --i) {
        final Animation a = animations.get(i);

        temp.clear();
        more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
        // 合成子 Animation 的操作,Transformation.compose() 方法做的就是矩阵相乘,透明度相乘叠加
        // 裁剪区域叠加。
        t.compose(temp);

        started = started || a.hasStarted();
        ended = a.hasEnded() && ended;
    }
	
    if (started && !mStarted) {
        if (mListener != null) {
            mListener.onAnimationStart(this);
        }
        mStarted = true;
    }
	// 当所有的子动画结束时,结束本身
    if (ended != mEnded) {
        if (mListener != null) {
            mListener.onAnimationEnd(this);
        }
        mEnded = ended;
    }

    return more;
}
复制代码

五、拓展实现示例

支持属性 direction,指定初始运行方向为 FORWARDS / BACKWORDS,结合 RepeatMode 就能支持循环的四种模式。

实现方式:根据指定 direction 模式,取反向变化率,再进行变化。

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    // mDirection 为自定义属性
    if (mDirection == BACKWORDS) {
        interpolatedTime = 1 - interpolatedTime;
    }
    // 动画实现细节
}
复制代码

你可能感兴趣的:(Android Animation 执行原理)