/** * <p>Cause an invalidate to happen on the next animation time step, typically the * next display frame.</p> * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * * @see #invalidate() */ public void postInvalidateOnAnimation() { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateOnAnimation(this); } }
如同注释所讲,会在下一个Frame开始的时候,发起一些invalidate操作,
ViewRootImpl的dispatchInvalidateOnAnimation():
public void dispatchInvalidateOnAnimation(View view) { mInvalidateOnAnimationRunnable.addView(view); }
mInvalidateOnAnimationRunnable 是一个 InvalidateOnAnimationRunnable:
public void addView(View view) { synchronized (this) { mViews.add(view); postIfNeededLocked(); } }而postIfNeededLocked()干的事情就是把mInvalidateOnAnimationRunnable 作为Choreographer.CALLBACK_ANIMATION(这个类型的task会在mesaure/layout/draw之前被运行)的Task 交给 UI线程的Choreographer.
而该runnable真正干的事情是:
@Override public void run() { final int viewCount; final int viewRectCount; synchronized (this) { mPosted = false; viewCount = mViews.size(); if (viewCount != 0) { mTempViews = mViews.toArray(mTempViews != null ? mTempViews : new View[viewCount]); mViews.clear(); } viewRectCount = mViewRects.size(); if (viewRectCount != 0) { mTempViewRects = mViewRects.toArray(mTempViewRects != null ? mTempViewRects : new AttachInfo.InvalidateInfo[viewRectCount]); mViewRects.clear(); } } for (int i = 0; i < viewCount; i++) { mTempViews[i].invalidate(); mTempViews[i] = null; } for (int i = 0; i < viewRectCount; i++) { final View.AttachInfo.InvalidateInfo info = mTempViewRects[i]; info.target.invalidate(info.left, info.top, info.right, info.bottom); info.recycle(); } }可以看到就是将添加到此runnbable中的view或者View的某块区域(rect)全部invalidate。
这样在后面的draw的时候就知道应该重绘哪些了.
View的invalidate会进一步触发ViewRootImpl的invalidateChildInParent()->invalidate()<一种情况(dirty == null 表示全部重绘),不过另外一种差不多>:
@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 首先检查是不是UI线程 checkThread(); if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); dirty == null 代表着全部重绘 if (dirty == null) { invalidate(); return null; 否则如果没有dirty的区域并且当前也没有动画,那么直接结束 } else if (dirty.isEmpty() && !mIsAnimating) { return null; } 下面的是将当前的dirty区域集合window做一些裁剪和交集 if (mCurScrollY != 0 || mTranslator != null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY != 0) { dirty.offset(0, -mCurScrollY); } if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } } 注意这里 localDirty 已经指向 mDirty了 final Rect localDirty = mDirty; if (!localDirty.isEmpty() && !localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } // Add the new dirty rect to the current one localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region final float appScale = mAttachInfo.mApplicationScale; 将dirty的区域与window区域相交(超过了window的不用画) final boolean intersected = localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (!intersected) { localDirty.setEmpty(); } 如果之后不会有draw的安排(performTraversals() 会在开始将mWillDrawSoon设为true) 并且dirty和window有交集或者有动画,那么就schedule一个traversal来进行重绘. if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); } return null; } void invalidate() { mDirty.set(0, 0, mWidth, mHeight); scheduleTraversals(); }而scheduleTraversals:
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); scheduleConsumeBatchedInput(); } }因为本次Frame的现在只运行到了CALLBACK_ANIMATION阶段,CALLBACK_TRAVERSAL还没有运行到,那么doTranversal也就没有被运行到,
那么mTraversalScheduled就还是true,这样其实是没有真正发出Traversal_callback的,
不过虽然没有发出去,不代表这次invalidate就不会生效,因为前面的invalidate()里已经设置了mDirty了:
而mDirty会在ViewRootImpl的draw函数里被使用来得到哪些rect需要重绘,
而刚好,在本次Frame的CALLBACK_ANIMATION完了以后的CALLBACK_TRAVERSAL会被运行,performTraversals()->performDraw()->draw(), 这样,就在本次Frame
就完成了对invalidate的区域的重绘.
注意也就是 通过 以CALLBACK_ANIMATION形式送到Choreographer来保证了在下一个Frame的时候进行invalidate。