View 动画源码分析

前言

开发过程中一直很好奇,TranslateAnimation、ScaleAnimation 这些 View 的动画究竟是在哪里执行的?为什么 View 动画不会改变 View 属性,为了了解其实现于是就调试了源码,本文用作记录。

基本使用

View 动画的使用方法非常简单,可以借助 XML 文件或者直接使用代码。下面以直接使用代码为例。

fun scale(view: View) {
val scaleAnimation = ScaleAnimation(0f, 1f, 0f, 1f, RELATIVE_TO_SELF, 0.5f, RELATIVE_TO_SELF, 0.5f)
scaleAnimation.duration = 2000
scaleAnimation.repeatCount = -1;
view.startAnimation(scaleAnimation)
}

上述代码就能让一个 View 不断的进行缩放。核心也就是创建了一个 Animation 实例,然后调用 View 的 startAnimation 方法。

源码分析

直接看看 View 的 startAnimation 方法源码实现,看看究竟是在哪里执行动画的。

// View.java
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
public void setStartTime(long startTimeMillis) {
mStartTime = startTimeMillis;
mStarted = mEnded = false;
mCycleFlip = false;
mRepeated = 0;
mMore = true;
}
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
if (animation != null) {
// 屏幕关闭时的处理逻辑,暂时忽略掉
if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
&& animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
}
animation.reset(); // 重置标志
}
}
protected void invalidateParentCaches() {
if (mParent instanceof View) {
((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED; // 设置一个标记
}
}

最后调用了 invalidate 方法,咦!这个方法不是用来触发 View 重绘的吗?难道动画是在 View 的 draw 方法中被执行的吗?还是继续跟进下源码,看看到底怎么回事。

// View.java
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()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) { // 相当复杂的判断
...
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);
p.invalidateChild(this, damage);
}
}
}

invalidateInternal 方法进行了判断是否需要继续执行 ViewParent.invalidateChild 方法,这里不去管它就当它返回 true (调试时发现不一定总是 true,后面有空研究再说)

public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
onDescendantInvalidated(child, child);
return;
}
... // 非硬件加速的情况
}
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
...
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
}

由于默认都是开启硬件加速的因此只分析暂时不关注非硬件加速的情况。一层层的向上调用,最终会调用到 ViewRootImpl 的 onDescendantInvalidated 方法

// ViewRootImpl.java
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
invalidate();
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}

最终调用了到了 scheduleTraversals 根据屏幕刷新机制,这个方法内部会去请求 vSync 信号,然后在 vSync 信号到来之时立即执行 performTraversals 方法(通过同步屏障),不过由于没有 invalidate 触发的 performTraversal 方法不会再重新执行 measure 以及 layout 流程,只会重新进行 draw 流程。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
Transformation transformToApply;
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
transformToApply = parent.getChildTransformation();
}
if (transformToApply != null
|| alpha < 1 || !hasIdentityMatrix() || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (transformToApply != null || !childHasIdentityMatrix) {
if (transformToApply != null) {
if (concatMatrix) { // 具体变化不知道怎么做的,不过应用 Matrix 给了 RenderNode 或者 Canvas 了
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
}
}
}
}
return more; // 返回值如果是 true 那么会继续重绘
}
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, Animation a, boolean scalingRequired) {
final boolean initialized = a.isInitialized();
if (!initialized) {
// 初始化 Animation 执行 initialize 方法
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop); // 执行 FillBefore 操作
}
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f); // 执行 Animation.applyTransformation 进行当前帧操作
...
return more;
}

这里拿到了刚刚设置了 Animation 实例,同时调用了 applyLegacyAnimation 方法。里面会去计算当前需要展示第几帧,然后调用 Animation.applyTransformation 应用变化内部会对 Matrix 做一系列变化,最后再把应用变化后的 Matrix 应用到 RenderNode 或者 Canvas 上。

总结

调用 setAnimation 并不立即会执行动画,而是会通知 viewRootImpl 重绘,进而去请求 vSync 信号,到信号来到时执行 viewRootImpl.performTraversals 对 view 进行重绘,这时候如果发现有动画,会根据当前时间以及动画开始时间接口差值器计算出一个进度,然后执行 animation.applyTransform 应用动画到 transform 内部的 matrix 上,最后取出 matrix 应用到 canvas 或者 renderNode 上。而由于 View 动画是通过是应用在 renderNode 或者 canvas 上,因此其只改变了显示位置,而没有改变 view 的属性。

0%