该函数的作用是请求View树进行重绘,当应用程序需要重绘某个视图时,可以调用该函数。
绘 制流程中,首先绘制最底层的根视图,然后再绘制其包含的子视图。子视图或者是一个
VeiwGroup,或者是一个View,如果是ViewGroup的话,则继续再绘制ViewGroup内部的子视图,绘
制过程一般并不会对所有视图进行重绘,而仅绘制那些“需要重绘” 的视图。那么,什么是“需要绘制”
的视图呢?View类内部变量mPrivateFlags中包含了一个标志位DRAWN,当该视图需要绘制时,就给
mPrivateFlags中添加该标识位。函数invalidate()的作用就是要根据所有视图中的该标志位计算具体哪个
区域需要重绘,这个区域将用一个矩形标识,并最终将这个矩形存放到ViewRoot类中的mDirty变量中,
之后的重绘过程将重绘所有包含在该mDirty区中的视图。
下面来分析该函数的具体执行流程
- 首先,查看视图中变量mPrivateFlags是否有DRAWN标识,并且视图的大小是否为0。如果不是,
则不需要重绘,直接返回;如果需要重绘,则先将需要重绘的区域保存到临时变量dirty中,这是一个
矩形变量,然后调用父视图mParent的 invalidateChild(this, dirty)函数,通知父视图该区域需要重绘,参
数 this代表该视图对象本身,dirty的大小默认就是该视图的大小
public void invalidate(Rect dirty) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
if (p != null && ai != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
final Rect r = ai.mTmpInvalRect;
r.set(dirty.left - scrollX, dirty.top - scrollY,
dirty.right - scrollX, dirty.bottom - scrollY);
mParent.invalidateChild(this, r);
}
}
}
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*
*/
public final void invalidateChild(View child, final Rect dirty) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD);
}
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
/**
* 定义一个int[2]型局部变量location,保存子视图左上角坐标,该坐标是相对于父视图的。这里
* 仅仅是定义,该变量引用了 mAttachlnfo 的 mlnvalidateChildLocation
*/
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
// If the child is drawing an animation, we want to copy this flag onto
// ourselves and the parent to make sure the invalidate request goes
// through
/**
* 2 判断子视图当前是否正在进行动画,如果正在进行动画,子 视 图 的 mPrivateFlags会包含
* DOAW_AINIMATIO
*/
final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
// Check whether the child that requests the invalidate is fully opaque
/**
* 判断子视图是否是不透明的。不透明的条件有三,首先该视图的isOpaque()必须返回true; 其
* 次是当前没有进行动画;最后是getAnimationO不为空。判断完毕后,将结果保存到变量isOpaque中
*/
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
child.getAnimation() != null;
// Mark the child as dirty, using the appropriate flag
// Make sure we do not set both flags at the same time
final int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY;
/**
* 根据该child的以上判断结果,为其父视图添加不同的属性,换句话说,子视图的属性会改变
* 父视图的属性。View系统在后面的绘制过程中需要了解到这种关系,以便进行不同方式的处理
*/
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
/**
* 首先,如果子视图正在进行动画,那么需要父视图也添加动画表示,而父视图要么是ViewGroup
* 对象,要么是ViewRoot对象。对 于 ViewGroup,给 mPrivateFlags变量中添加DRAW—ANIMATION标
* 识;而对于ViewRoot,则给其内部变量mlsAnamating赋值为true。
*/
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= DRAW_ANIMATION;
} else if (parent instanceof ViewRoot) {
((ViewRoot) parent).mIsAnimating = true;
}
}
/**
* 设置dirty标识。如果子视图是不透明的,则父视图的dirty标识是DIRTY_OPAQUE,否则就
* 是 DIRTY。这两个标识含义的共同点是都需要重绘,区别在于如果是DIRTY_OPAQUE,父视图可以暂
* 时不重绘,因为子视图是不透明的
*/
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null && (view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
}
/**
* ) 调 用 parent.invalidateChildInparent()函数。这 里 parent如果写成view大家可能更容易理解,
* 实际上parent变量和view变量在这里是完全相同的,该函数的返回值是该视图的父视图。返回值的类
* 型有两种情况,一种是ViewRoot, —种是ViewGroup。由于第4 步代码使用do{}while()语句,当调用
* invalidateChildInparent()返回parent后,会循环执行该步骤,直到返回的parent为空,即循环到View树
* 的根视图。
* 从 最 初 的 child到所有父视图都设置好了绘制所需要的标识,包括
* DRAW—AINIMATION、DIRTY OPAQUE、DIRTY
*/
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
}
}
下面继续来看ViewGroup以及ViewRoot中对invalidateChildInParent()函数的不同实现
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD_IN_PARENT);
}
/**
* 1 判断该视图的mPrivateFlags是否也已经包含DRAWN标识。该标识的含义是视图是否已经绘
* 制到屏幕上了,这与视图的VISIBLE状态有所不同,就算视图是VISIBLE的,但是如果视图没有显示
* 到屏幕上,则其DRAWN标识依然为false。
*2 如果已经完成动画,或者没有动画,则寻找此参数中dirty区和亥视图显示区的合集,判断条
* 件如以下代码所示,其中引用了两个标识,分 别 是 FLAG_OPTIMAZE—INVALIDE和FLAG_ANIMATION_DONE
* 这段代码的逻辑判断中使用了 if(flags&(X|Y) !=X)这样的语法,其语义如下。
* 要 么 flags中没有X 标识,要么是同时有X 和 Y 标识,翻译一下就是要么没有动画,要么有动画
* 同时通话已经完成。对于这两种条件,处理逻辑是相同的,那就是首先对dirty区域进行offset(),参数
* location[]保存的是子视图child在父视图中的位置,经过offset()后,就将该区域转换到了子视图在父视
* 图显示区中的位置,然后再经过intersect)运算,最终找到交叉区,该过程如图13-22所示。
*/
if ((mPrivateFlags & DRAWN) == DRAWN) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
final int left = mLeft;
final int top = mTop;
/**
* 给 location□赋值为该 ViewGroup 的 mLeft 和 mTop,并返回该 ViewGroup 的 mParent。
*/
if (dirty.intersect(0, 0, mRight - left, mBottom - top) ||
(mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
return mParent;
}
} else {
mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
dirty.set(0, 0, mRight - location[CHILD_LEFT_INDEX],
mBottom - location[CHILD_TOP_INDEX]);
return mParent;
}
}
return null;
}
下面再来看ViewRoot中是如何处理invalidateChildInParent()的。ViewRoot中该函数仅仅是调用
ViewRoot中的invalidateChild(),这个函数与ViewGroup中的函数同名,注意区分。并且,在 ViewRoot
中,invalidateChildO中的第一个参数没有任何用处,
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
invalidateChild(null, dirty);
return null;
}
public void invalidateChild(View child, Rect dirty) {
/**
* 1 调用checkThread(),确保该函数是从U I线程中执行
*/
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
/**
* 对 dirty区进行一定的调整,主要是把dirty区转换到ViewRoot中的屏幕坐标上,具体包括:
*/
if (mCurScrollY != 0 || mTranslator != null) {
/**
* 1) 同样调用offset()方法,将 dirty区转换到ViewRoot的屏幕显示区
* 2) 如果该ViewRoot包 含 Translator对 象 mTranslater,则请求该对象对该dirty区域进行转换。
* Translator对象只有当屏幕的物理像素和程序的逻辑像素不相等时才存在,比如当屏幕的分辨率为
* 800x480像素,而 Framework却把程序窗口配置成480X320像素时,于是对于dirty这个矩形区,需要
* 进行一定的转换
*/
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
/**
* 调用dirty.inset(-1,1)对区域进行内嵌1像素,这仅在mAttachInfo的mScaling为true
* 时才执行
*
*/
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
/**
* 在 ViewRoot类中有一个mDirty矩形变量,它保存了上
* 一 次 invalidate所 产 生 的 dirty区,应用程序可以在一次
* MessageQueue消息处理过程中多次调用invalidate()函数,从而不
* 断更新ViewRoot中的mDirty变量。本步骤中正是把新的dirty添
* 加到 mDirty变量中。
*/
mDirty.union(dirty);
/**
* 4 此时,万事倶备,应该请求View树进行重绘了。在发出
* 请求之前,先查看是否已经发出过请求,并 且 View 内部是否还
* 没有处理该请求。如果是,则不用再次发送。代码中是通过变量
* mTraversalScheduled查看是否发出过请求,该变量会在执行
* performTraversals()方法开始前被置为false
* 所示
*/
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
至此,一 次 invalidate()就结束了,回顾一下,它大致做了两
件事情。其一是给所有需要重绘的视图添加了一个DIRTY或者
DIRTY_OPAQUE标记;其二是通过矩形运算,找到真正需要重
绘的矩形区,并将其保存在了 ViewRoot类中的mDirty变量中。
有了这两个信息,View树重绘就能够决定通知哪些View进行重
绘,并告诉它们应该重绘什么区域