介绍
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;
}
结论
- 动画的原理都是生成Matrix,对Canvas进行映射
- 补间动画使用的是RenderProperties.h中mAnimationMatrix(也可能未set),它的生命周期较短
- 同时在执行Animation && 同一父View,在一帧内就被clear
- 再次执行到draw方法里面,被clear
- 属性动画使用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;