/**
* <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。