Animation动画绘制流程

介绍

Animation包括平移、旋转、透明度、缩放等,它们都继承自 Animation 类,并在applyTransformation方法中通过操作Transformation实现各种各样的动画。先看一下Animation的使用:

scss 复制代码
TranslateAnimation animation = new TranslateAnimation(0.0f, 30.0f, 0.0f, 30.0f);
animation.setDuration(3000);
view.startAnimation(animation);

本文将基于Android 8.0.0(API26)简要分析补间动画的绘制流程

源码分析

View的startAnimation如下

scss 复制代码
//frameworks/base/core/java/android/view/View.java
    public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }

setStartTime将mStartTime设为-1,对animation进行初始化

ini 复制代码
//frameworks/base/core/java/android/view/animation/Animation.java
    public static final int START_ON_FIRST_FRAME = -1;

    public void setStartTime(long startTimeMillis) {
        mStartTime = startTimeMillis;
        mStarted = mEnded = false;
        mCycleFlip = false;
        mRepeated = 0;
        mMore = true;
    }

setAnimation将animation赋值给mCurrentAnimation

scss 复制代码
 //frameworks/base/core/java/android/view/View.java    
    public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;
        if (animation != null) {
            animation.reset();
        }
    }

invalidateParentCaches对父View设置PFLAG_INVALIDATED,标记父View要重建displaylist

typescript 复制代码
 //frameworks/base/core/java/android/view/View.java        
    protected void invalidateParentCaches() {
        if (mParent instanceof View) {
            ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
        }
    }

前3个方法都比较简单,动画看起来没有start。我们先分析invalidate方法,见附录1。 接着去看draw方法,Why?

  • invalidate方法会导致view的draw被调用
  • 检查mCurrentAnimation被调用的地方,发现在draw方法中有一些操作
scss 复制代码
 //frameworks/base/core/java/android/view/View.java      
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ···
        final Animation a = getAnimation();
        if (a != null) {
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        }
        ···
    }

在applyLegacyAnimation方法中,getTransformation返回一个bool值表示动画是否执行完成,这将决定是否继续调用invalidate

java 复制代码
 //frameworks/base/core/java/android/view/View.java      
    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        ···
        final Transformation t = parent.getChildTransformation();
        boolean more = a.getTransformation(drawingTime, t, 1f);
        ···
        if (more) {
            //Alpha动画返回false,其他动画返回true
            if (!a.willChangeBounds()) {
              parent.invalidate(mLeft, mTop, mRight, mBottom);
            } else {
                ···
                //由于animation动画绘制的区域可能发生改变,需要重新计算damge
                parent.invalidate(left, top, left + (int) (region.width() + .5f),
                        top + (int) (region.height() + .5f));
            }
        }
        return more;
    }

getChildTransformation返回的Transformation对象包装了Matrix和Alpha

ruby 复制代码
//frameworks/base/core/java/android/view/ViewGroup.java    
    Transformation getChildTransformation() {
        if (mChildTransformation == null) {
            mChildTransformation = new Transformation();
        }
        return mChildTransformation;
    }

//frameworks/base/core/java/android/view/animation/Transformation.java
public class Transformation {
    ...
    protected Matrix mMatrix;
    protected float mAlpha;
    ...
}

Animation对象的getTransformation通过currentTime计算出当前的进度,调用applyTransformation,该方法由Animation的子类实现。

ini 复制代码
//frameworks/base/core/java/android/view/animation/Animation.java
public boolean getTransformation(long currentTime, Transformation outTransformation) {
        if (mStartTime == -1) {
            mStartTime = currentTime;
        }
        float normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / (float) duration;
        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
        ···
        applyTransformation(interpolatedTime, outTransformation);
        ···
    }

看一下Translate动画的applyTransformation,它通过currentTime计算出当前的值,并设置到Transformation对象的Matrix上,这里仍未对canvas有操作

scss 复制代码
//frameworks/base/core/java/android/view/animation/TranslateAnimation.java
    protected void applyTransformation(float interpolatedTime, Transformation t){
        ...
        if (mFromXDelta != mToXDelta) {
            dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
        }
        if (mFromYDelta != mToYDelta) {
            dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
        }
        t.getMatrix().setTranslate(dx, dy);
    }

接着查看Transformation对象的mMatrix在draw方法中的调用:

  • 在支持硬件加速的设备上,调用rendernode的setAnimationMatrix
  • 未支持硬件加速的设备上,直接将canvas与该动画矩阵相乘
scss 复制代码
//frameworks/base/core/java/android/view/View.java
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...
        //a就是animation对象,Alpha动画返回false,其他动画会返回true
        concatMatrix = a.willChangeTransformationMatrix();
        if (transformToApply != null) {
            if (concatMatrix) {
                if (drawingWithRenderNode) {
                    renderNode.setAnimationMatrix(transformToApply.getMatrix());
                } else {
                    canvas.concat(transformToApply.getMatrix());
                }
                parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
            }
            ...
            }
        }
    }

在调用renderNode的setAnimationMatrix时,通过JNI最终会调用到RenderProperties.h的setAnimationMatrix,该方法将对成员变量mAnimationMatrix进行赋值

scss 复制代码
//frameworks/base/core/java/android/view/RenderNode.java
public boolean setAnimationMatrix(Matrix matrix) {
    return nSetAnimationMatrix(mNativeRenderNode,
            (matrix != null) ? matrix.native_instance : 0);
}

//frameworks/base/core/jni/android_view_RenderNode.cpp
static jboolean android_view_RenderNode_setAnimationMatrix(jlong renderNodePtr, jlong matrixPtr) {
    SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
    return SET_AND_DIRTY(setAnimationMatrix, matrix, RenderNode::GENERIC);
}

#define SET_AND_DIRTY(prop, val, dirtyFlag) 
    (reinterpret_cast<RenderNode*>(renderNodePtr)->mutateStagingProperties().prop(val) ? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true) : false)
RenderNode的mutateStagingProperties()方法返回的是RenderProperties对象
//frameworks/base/libs/hwui/RenderNode.h
RenderProperties& mutateStagingProperties() {
    return mStagingProperties;
}

mAnimationMatrix是通过传递进来的matrix对象new出来的

arduino 复制代码
//frameworks/base/libs/hwui/RenderProperties.cpp
    bool setAnimationMatrix(const SkMatrix* matrix) {
        delete mAnimationMatrix;
        if (matrix) {
            mAnimationMatrix = new SkMatrix(*matrix);
        } else {
            mAnimationMatrix = nullptr;
        }
        return true;
    }

接着去看mAnimationMatrix在什么时候被set到canvas上

scss 复制代码
//frameworks/base/libs/hwui/FrameBuilder.cpp
mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);

//frameworks/base/libs/hwui/RenderNode.cpp
void RenderNode::computeOrderingImpl(){
    ...
    opState->transformFromCompositingAncestor = localTransformFromProjectionSurface;
    applyViewPropertyTransforms(localTransformFromProjectionSurface);
    ...
}

void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) const {
    ...
    } else if (properties().getAnimationMatrix()) {
        mat4 anim(*properties().getAnimationMatrix());
        matrix.multiply(anim);
    }
    ...
}

至此,Animation动画的整体流程已经走完。

QA

QA1:为什么Animation执行Translate后点击无效?

先看一下ViewGroup的dispatchTouchEvent方法

typescript 复制代码
//frameworks/base/core/java/android/view/ViewGroup.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ···
    //for循环遍历子view
        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            // Child wants to receive touch within its bounds.
            ···
            break;
        }
        ···
    }

dispatchTransformedTouchEven中通过child.hasIdentityMatrix()判断view的mTransformMatrix是否为单位矩阵,由于Animation动画只是修改了mAnimationMatrix(mTransformMatrix应该为单位矩阵),这里就走了正常的事件分发。

arduino 复制代码
//frameworks/base/core/java/android/view/ViewGroup.java
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        ···
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
        handled = child.dispatchTouchEvent(transformedEvent);
        ···
    }

hasIdentityMatrix方法如下

scss 复制代码
//frameworks/base/core/jni/android_view_RenderNode.cpp
    static jboolean android_view_RenderNode_hasIdentityMatrix(jlong renderNodePtr) {
        RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
        renderNode->mutateStagingProperties().updateMatrix();
        return !renderNode->stagingProperties().hasTransformMatrix();
    }

hasTransformMatrix方法中判断矩阵不为空且矩阵是单位矩阵

scss 复制代码
///frameworks/base/libs/hwui/RenderProperties.h
    bool hasTransformMatrix() const {
        return getTransformMatrix() && !getTransformMatrix()->isIdentity();
    }
    const SkMatrix* getTransformMatrix() const {
        return mComputedFields.mTransformMatrix;
    }

QA2:为什么Animator执行Translate后点击有效?

属性动画是通过反射调用了View的setTranslationX等方法,它在C层的调用如下RP_SET_AND_DIRTY将translationX赋值给mTranslationX,并将mMatrixOrPivotDirty设为true

arduino 复制代码
//frameworks/base/libs/hwui/RenderProperties.cpp
    bool setTranslationX(float translationX) {
        return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationX, translationX);
    }

在调用到hasIdentityMatrix方法时会触发updateMatrix,最终修改了mTransformMatrix。

scss 复制代码
//frameworks/base/libs/hwui/RenderProperties.cpp
void RenderProperties::updateMatrix() {
    if (mPrimitiveFields.mMatrixOrPivotDirty) {
        if (!mComputedFields.mTransformMatrix) {
            mComputedFields.mTransformMatrix = new SkMatrix();
        }
        SkMatrix* transform = mComputedFields.mTransformMatrix;
        transform->reset();
        if (MathUtils::isZero(getRotationX()) && MathUtils::isZero(getRotationY())) {
            transform->setTranslate(getTranslationX(), getTranslationY());
            transform->preRotate(getRotation(), getPivotX(), getPivotY());
            transform->preScale(getScaleX(), getScaleY(), getPivotX(), getPivotY());
        }
        ···
        mPrimitiveFields.mMatrixOrPivotDirty = false;
    }
}

在dispatchTransformedTouchEvent中mTransformMatrix非单位矩阵,则获取mTransformMatrix的逆矩阵对MotionEvent进行映射,使View可以响应该事件。

QA3:mAnimationMatrix、mTransformMatrix的区别

  • mAnimationMatrix来自于Animation计算的值,JAVA层子View是同一个对象;
  • mTransformMatrix是根据View的setXXX方法,修改了RenderNode属性,最终计算出了该Matrix

QA4:能否重写disptachTouchEvent用Animation的矩阵映射达到Animation动画也可以点击?

  • 获取RenderNode中的mAnimationMatrix可行吗? 否,RenderProperties.h中的mAnimationMatrix不一定被set(不支持硬件加速的设备),且JAVA层获取不到mAnimationMatrix对象(RenderNode.java没有提供get方法)
  • 父View的getChildTransformation来获取矩阵可行吗? 否,这个Matrix来源于父View中的getChildTransformation,所有子view共享此对象。使用完Matrix后会添加一个标记位parent.mGroupFlags = ViewGroup.FLAG_CLEAR_TRANSFORMATION;,再次执行到draw方法会重置Matrix
scss 复制代码
   if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
       parent.getChildTransformation().clear();            
       parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
   }

结论

  1. 动画的原理都是生成Matrix,对Canvas进行映射
  2. 补间动画使用的是RenderProperties.h中mAnimationMatrix(也可能未set),它的生命周期较短
    1. 同时在执行Animation && 同一父View,在一帧内就被clear
    2. 再次执行到draw方法里面,被clear
  3. 属性动画使用RenderProperties.h中mTransformMatrix,该Matrix来自于对View设置的一些属性,它并不改变View的left、top、right、bottom

附录

invalidate()

invalidate会调用到invalidateInternal方法

typescript 复制代码
public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

invalidateInternal中调用viewParent的invalidateChild

java 复制代码
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    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);
    }
}

父view的invalidateChild方法中会一直循环执行其父view的invalidateChildInParent方法,直到parent为null

ini 复制代码
        do {
            ···
            parent = parent.invalidateChildInParent(location, dirty);
        ···
            }
        } while (parent != null);

由于DecorView的父布局是ViewRootImpl(它不是ViewGroup),需要再查看。

kotlin 复制代码
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }
    ···
        invalidateRectOnScreen(dirty);
        return null;
    }

在invalidateRectOnScreen方法中会执行scheduleTraversals,对布局进行遍历。

scss 复制代码
private void invalidateRectOnScreen(Rect dirty) {
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

scheduleTraversals方法中发送了一个同步锁,阻塞队列中的同步消息,通过mChoreographer向底层注册一帧的callback,具体是由mDisplayEventReceiver.scheduleVsync();执行native方法对下次vsync信号进行监听,当信号来时执行onVsync方法。此外,它还将mTraversalRunnable放入mCallbackQueues中

ini 复制代码
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ···
        }
    }

mDisplayEventReceiver(实现了Runnable接口)的onVsync回调中发送了一个异步消息,消息的callback为this,在run方法中执行doFrame方法

java 复制代码
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
        mHavePendingVsync = false;
        doFrame(mTimestampNanos, mFrame);
    }

doFrame方法中会执行doCallbacks,从mCallbackQueues中拿到刚才放入的mTraversalRunnable并执行。

java 复制代码
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

doTraversal中,首先将mTraversalScheduled设置为false,这个标记位保证了在下一次vsync信号到来之前mChoreographer只向底层注册一次信号(过滤掉同一帧内的重复调用,只执行一次performTraversals)。

scss 复制代码
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            performTraversals();
        }
    }

performTraversals对view树进行遍历,执行测量、布局、绘制(maybe)。

scss 复制代码
    private void performTraversals() {
        if (mFirst) {
            ···
        //view.post()方法会在这里执行
            host.dispatchAttachedToWindow(mAttachInfo, 0);
        }
        ···
        performMeasure();
        ···
        performLayout();
        ···
        performDraw();
        ···

RenderProperties.h

补间动画改变的矩阵:mAnimationMatrix 属性动画中改变的属性:mAlpha、mTranslationX、mTranslationY......

ini 复制代码
//frameworks/base/libs/hwui/RenderProperties.h
private:
    // Rendering properties
    struct PrimitiveFields {
        int mLeft = 0, mTop = 0, mRight = 0, mBottom = 0;
        int mWidth = 0, mHeight = 0;
        int mClippingFlags = CLIP_TO_BOUNDS;
        float mAlpha = 1;
        float mTranslationX = 0, mTranslationY = 0, mTranslationZ = 0;
        float mElevation = 0;
        float mRotation = 0, mRotationX = 0, mRotationY = 0;
        float mScaleX = 1, mScaleY = 1;
        float mPivotX = 0, mPivotY = 0;
        bool mHasOverlappingRendering = false;
        bool mPivotExplicitlySet = false;
        bool mMatrixOrPivotDirty = false;
        bool mProjectBackwards = false;
        bool mProjectionReceiver = false;
        Rect mClipBounds;
        Outline mOutline;
        RevealClip mRevealClip;
    } mPrimitiveFields;

    SkMatrix* mStaticMatrix;
    SkMatrix* mAnimationMatrix;
    LayerProperties mLayerProperties;

    struct ComputedFields {
        ComputedFields();
        ~ComputedFields();
        SkMatrix* mTransformMatrix;
        Sk3DView mTransformCamera;
        bool mNeedLayerForFunctors = false;
    } mComputedFields;
相关推荐
开心呆哥1 分钟前
【如何使用 ADB 脚本批量停止 Android 设备上的所有应用】
android·adb
CYRUS STUDIO1 小时前
ARM64汇编寻址、汇编指令、指令编码方式
android·汇编·arm开发·arm·arm64
weixin_449310842 小时前
高效集成:聚水潭采购数据同步到MySQL
android·数据库·mysql
Zender Han2 小时前
Flutter自定义矩形进度条实现详解
android·flutter·ios
白乐天_n4 小时前
adb:Android调试桥
android·adb
姑苏风8 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k11 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小1012 小时前
JavaWeb项目-----博客系统
android
风和先行12 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.13 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list