View动画,即补间动画。包含:渐变、旋转、平移、缩放四种基本的动画,当然,我们可以自己扩展实现。View动画不会改变View的属性,指数视觉效果变化,动画完成之后它还是在原本的位置上。
这篇文章主要着手于View动画的主流程进行分析,动画的呈现原理不在本文的分析范围之内(如Matrix之类的原理)。
文中所展示的源码为android-30
调试小技巧:用Android Studio创建一个模拟器,Android系统版本与demo的compileSdkVersion对应的版本一致,这样的话,在调试View、ViewGroup、ViewRootImpl等源码时非常方便,不会出现源码对不上的情况。
1. 回顾View动画基本用法
View动画大家已经非常熟悉了,举个简单例子简单回顾下:
val scaleAnimation = ScaleAnimation(0f, 1f, 0f, 1f)
scaleAnimation.duration = 1000
iv_animation_img.startAnimation(scaleAnimation)
此处列举了一个缩放动画,大小由0到1,再由0到1,动画时长为1000毫秒,然后iv_animation_img 这个View开始执行ScaleAnimation这个动画。不管是缩放动画,还是平移动画等,都是将animation参数配置好之后,交给View去执行。其实这里还可以通过xml来配置这些参数,原理是类似的。
2. 动画流程
因为开始动画是调用的startAnimation方法,所以我们就从这里入手。
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;
...
}
protected void invalidateParentCaches() {
if (mParent instanceof View) {
((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
}
}
startAnimation方法中,前面的setStartTime、setAnimation、invalidateParentCaches都是对参数进行初始化,invalidateParentCaches给mParent设置了一个标志位PFLAG_INVALIDATED 。最后一个调用的是invalidate,这个我们熟悉,就是在必要的时候重绘嘛。重绘和动画有什么关系?看一下具体实现
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 ((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)) {
...
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
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);
}
...
}
}
invalidate(true)中,感觉好像没干啥,就是调用mParent的invalidateChild,而mParent一般是ViewGroup,这里调用了ViewGroup的invalidateChild。
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
onDescendantInvalidated(child, child);
return;
}
ViewParent parent = this;
...
do {
...
parent = parent.invalidateChildInParent(location, dirty);
...
} while (parent != null);
}
从invalidateChild方法开始,就分流了,开启了硬件加速和未开启硬件加速,走的是不同的逻辑。下面我们分别来分析:
2.1 invalidate(true)
在invalidateChild方法中,开启了硬件加速也就是会走onDescendantInvalidated方法:
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
...
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
}
onDescendantInvalidated中主要是调用mParent.onDescendantInvalidated方法,具体到某个View的话,它的mParent应该是ViewGroup,而ViewGroup的mParent也应该是ViewGroup,相当于沿着View树不断地向上调用父View的onDescendantInvalidated,知道View树的最顶层View。最顶层View,也就是DecorView。在调用DecorView的onDescendantInvalidated方法时,也会去调用mParent.onDescendantInvalidated方法,那么此时的mParent是什么呢?是ViewRootImpl。也就是说,mParent.onDescendantInvalidated最终会调用到ViewRootImpl的onDescendantInvalidated:
@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
...
invalidate();
}
ViewRootImpl的onDescendantInvalidated异常简单,就是调用了下invalidate()。
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
如果正在执行performTraversals方法,那么mWillDrawSoon会被赋值为true,这里应该是为了避免同一时间多次调用scheduleTraversals。当我们没有在执行performTraversals方法时,mWillDrawSoon为false,此时进入到scheduleTraversals。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这个方法大家应该比较熟悉了,它是View三大绘制流程的起点,开启同步屏障,然后通过Choreographer注册vsync信号的观察者,等待着vsync信号的来临,来临之后开始调用performTraversals方法,执行View绘制的三大流程。
不太清楚这块儿的同学可以看之前的文章:
我们接着分析,当我们调用startAnimation开始动画之后,到现在,还没看到动画相关的东西。仅仅是让View重新走performTraversals。既然是仅仅调用了performTraversals方法,那动画是什么时候执行的?现在只能先猜了,可能动画是在performTraversals中的三大流程中去执行的。
- 首先看一下是否在performMeasure中执行的:
private void performTraversals() {
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
...
windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
}
...
}
在measureHierarchy内部会执行performTraversals进行协商测量,在执行measureHierarchy之前首先mLayoutRequested得为true才行。mLayoutRequested在requestLayout中会被置为true,还有就是第一次执行performTraversals时也会赋值为true。而动画是从invalidate方法从下往上的,所以这里mLayoutRequested就是false,然后performMeasure不会被执行。
- 再看一下performLayout是否被执行:
private void performTraversals() {
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
...
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
...
}
...
}
performLayout和measureHierarchy的执行条件是一样的,也就是performLayout也不会执行。
- 最后看一下performDraw
private void performTraversals() {
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
...
performDraw();
}
...
}
只要View可见,就会执行performDraw。那我们接下来的重点就是要去理一理performDraw里面的逻辑了。performDraw里面会执行draw方法,draw方法内部
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
private boolean draw(boolean fullRedrawNeeded) {
final Rect dirty = mDirty;
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
}
}
}
在draw方法内部,因为mDirty在invalidate时设置了值,所以这里!dirty.isEmpty() 肯定是为true的,如果开启了硬件加速(一般都是开启的,从Android4.0开始,以“run fast, smooth, and responsively” 为核心目标对 UI 进行优化,应用默认都开启和使用硬件加速方式加速 UI 的绘制),则会走到硬件加速绘制流程:
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
...
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
...
}
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
updateViewTreeDisplayList(view);
...
}
private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}
走硬件绘制流程时会先走updateRootDisplayList遍历View树更新DisplayList,当然,这里是只更新那些打上PFLAG_INVALIDATED 标记的view的DisplayList。打上PFLAG_INVALIDATED 标记的View,也就是执行动画的那个view的parent view,在动画开始执行的时候就打上这个标记了。我们看下具体是怎么更新DisplayList的
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
if (!canHaveDisplayList()) {
return renderNode;
}
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.hasDisplayList()
|| (mRecreateDisplayList)) {
if (renderNode.hasDisplayList()
&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode;
}
mRecreateDisplayList = true;
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
} else {
draw(canvas);
}
}
} finally {
renderNode.endRecording();
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
protected void dispatchGetDisplayList() {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
recreateChildDisplayList(child);
}
}
...
}
private void recreateChildDisplayList(View child) {
child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
child.mPrivateFlags &= ~PFLAG_INVALIDATED;
child.updateDisplayListIfDirty();
child.mRecreateDisplayList = false;
}
在updateDisplayListIfDirty内部,如果是与动画不相关的view的话,则会执行dispatchGetDisplayList(),在dispatchGetDisplayList内部如果当前view是ViewGroup的话,则会遍历自己的子View,子View执行recreateChildDisplayList方法,在里面又执行updateDisplayListIfDirty方法。这样沿着View树逐渐向下传递。而如果mRecreateDisplayList为true,也就是与需要执行动画相关的view的时候,则会走到下面的dispatchDraw里面去。我们重点看下这个
@Override
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
...
for (int i = 0; i < childrenCount; i++) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
...
return more;
}
在ViewGroup执行dispatchDraw的内部,会通知自己的各个子View去draw,在draw里面的时候,会执行到applyLegacyAnimation了,看起来就是我们要找的了。
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();
boolean more = a.getTransformation(drawingTime, t, 1f);
...
return more;
}
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 || isCanceled();
mMore = !expired;
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
...
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 || 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;
}
在applyLegacyAnimation内部,首先是通知外面动画开始了。然后执行getTransformation(),这个是动画的核心部分。它内部主要做了以下几件事:
- 记录动画第一帧的时间
- 根据当前时间和第一帧的时间的差来计算当前动画的进度
- 根据插值器计算动画的实际进度
- 使用applyTransformation应用动画效果
- 判断是否为重复动画,重复动画如果没执行到足够的次数,则需要重置,然后再次执行
- 最后将是否需要继续执行动画给return回去
这里面的applyTransformation方法是用来应用动画效果的,它是Animation的方法,然后不同的Animation子类有自己相应的实现,下面我举个例子,看一下ScaleAnimation的实现
@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);
}
}
内部主要是利用Matrix去做一些运算,主要是算法相关的,我没继续深入了。
咱们再回到getTransformation的返回值上面来,如果还需要执行动画则会返回true。这个返回值是在上面的applyLegacyAnimation里面被接收到
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
...
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
if (more) {
if (!a.willChangeBounds()) {
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) {
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);
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;
}
在applyLegacyAnimation里面,我们执行了getTransformation并拿到了返回值。如果需要继续执行动画,则会给parent的mPrivateFlags赋值上PFLAG_DRAW_ANIMATION 这个flag,然后调用parent的invalidate,继续下一帧动画的执行,相当于把之前的流程再跑一遍。
现在,我们理清了。某个View需要执行动画时,先是初始化好一些基本信息,然后不是立马执行动画,而是走到ViewRootImpl里面注册VSYNC信号,等到下一次VSYNC来临时执行scheduleTraversals方法,在里面执行View的三大流程。但其实measure和layout没有执行到,因为不需要它们参与。在draw的过程中,顺便把动画给执行了,一次只执行一帧的动画,如果动画没有执行完,则调用invalidate继续下一帧动画的执行。在draw过程中也进行了优化,只有参与动画执行的view才需要走draw流程。
3. setFillAfter
众所周知,View 动画区别于属性动画的就是 View 动画并不会对这个 View 的属性值做修改,比如平移动画,平移之后 View 还是在原来的位置上,实际位置并不会随动画的执行而移动,这是什么原理?
首先,我们平时使用setFillAfter时是调用的Animation对象的方法,那么直接进Animation看一下
boolean mFillAfter = false;
public boolean getFillAfter() {
return mFillAfter;
}
public void setFillAfter(boolean fillAfter) {
mFillAfter = fillAfter;
}
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 || isCanceled();
mMore = !expired;
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
...
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}
...
}
Animation里面有mFillAfter属性和它的getter、setter方法,同时在getTransformation中也用到了这个属性,可能会走到applyTransformation里面的逻辑去。
再搜索一下getFillAfter使用的地方,发现在ViewGroup的finishAnimatingView中使用到了
void finishAnimatingView(final View view, Animation animation) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
if (disappearingChildren != null) {
if (disappearingChildren.contains(view)) {
disappearingChildren.remove(view);
if (view.mAttachInfo != null) {
view.dispatchDetachedFromWindow();
}
view.clearAnimation();
mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
}
}
if (animation != null && !animation.getFillAfter()) {
view.clearAnimation();
}
if ((view.mPrivateFlags & PFLAG_ANIMATION_STARTED) == PFLAG_ANIMATION_STARTED) {
view.onAnimationEnd();
view.mPrivateFlags &= ~PFLAG_ANIMATION_STARTED;
mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
}
}
这个parent.finishAnimatingView,是在View的boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) 中被调用的。如果不是fillAfter,则会调用view.clearAnimation(); ,看起来应该就是我们要找的了。
public void clearAnimation() {
if (mCurrentAnimation != null) {
mCurrentAnimation.detach();
}
mCurrentAnimation = null;
invalidateParentIfNeeded();
}
protected void invalidateParentIfNeeded() {
if (isHardwareAccelerated() && mParent instanceof View) {
((View) mParent).invalidate(true);
}
}
在View.clearAnimation() 中会把Animation属性清空,然后调用invalidateParentIfNeeded,接着会触发invalidate(true) ,接着会重新走一遍ViewRootImpl的performTraversals,然后走三大流程,其中measure和layout不会走,draw流程也只会走View动画相关的View,因为没有了Animation,那么就依然还是画在原来的位置了。
如果设置了fillAfter则不会走最后这一步,还是在原来的位置上。
4. View动画会导致measure吗?
既然 View 动画不会改变 View 的属性值,那么如果是缩放动画时,View 需要重新执行测量操作么?
从我们上面的源码分析中,我们发现,其实是不会走measure和layout的。
5. 小结
Animation动画内部是通过重绘申请,ViewRootImpl监听VSYNC信号,然后ViewRootImpl重新走View绘制的三大流程中的performDraw,遍历View树,如果遇到需要执行动画的View,则顺便把动画执行了。
6. 资料
|