invalidate和requestLayout方法源码分析
invalidate方法源码分析
在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewGroup没有重写这些方法),如下:
/**
* Mark the area defined by dirty as needing to be drawn. dirty代表需要重新绘制的脏的区域
* If the view is visible, onDraw(Canvas) will be called at some
point in the future.* This must be called from a UI thread. To call from a non-UI thread, call
postInvalidate().* <b>WARNING:</b> In API 19 and below, this method may be destructive to
dirty.* @param dirty the rectangle矩形 representing表示 the bounds of the dirty region地区
*/
public void invalidate(Rect dirty) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
dirty.right - scrollX, dirty.bottom - scrollY, true, false);
}
public void invalidate(int l, int t, int r, int b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
//实质还是调用invalidateInternal方法
invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}
/**
* Invalidate the whole view. 重新绘制整个View
*/
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
//这是所有invalidate的终极调用方法
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {......
// Propagate繁殖、传播 the damage损害的(只需要刷新的) rectangle to the parent view.
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);
//将需要刷新的区域封装到damage中p.invalidateChild(this, damage);
//调用Parent的invalidateChild方法,传递damage给Parent}
......
}
我们看下ViewGroup的invalidateChild方法:
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
......
do {
......
//循环层层上级调用,直到ViewRootImpl会返回null
parent = parent.invalidateChildInParent(location, dirty);
......
} while (parent != null);
}
这个问题之前有分析过,这里直接说结论了,就是循环当层层上级传递到ViewRootImpl时结束,我们看下ViewRootImpl的invalidateChildInParent的源码:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
......
//View调用invalidate最终层层上传到ViewRootImpl后最终触发了该方法
scheduleTraversals();
......
return null;
}
这里调用的scheduleTraversals会通过Handler发送一个异步消息,收到消息后会回调doTraversal方法,最终调用performTraversals执行重绘。
performTraversals方法就是整个View树开始绘制的起始节点,所以,View调用invalidate方法的实质是:层层上传到父级,直到传递到ViewRootImpl后会触发scheduleTraversals方法,然后整个View树就开始重新按照View的绘制流程进行重绘任务。
这里再按正序说下整个View树的绘图流程:
整个View树的绘图流程是在【ViewRootImpl】类的【performTraversals】方法开始的,该函数做的执行过程主要是根据之前设置的【状态】,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重新绘制(draw),其核心也就是通过判断来选择按顺序执行这三个方法中的哪几个。
到此View的invalidate方法原理就分析完成了。
invalidate方法执行的过程图:
postInvalidate方法源码分析
上面分析invalidate方法时注释中说该方法只能在UI Thread中执行,其他线程中需要使用postInvalidate方法,所以我们来分析分析postInvalidate这个方法源码。如下:
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
// 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;
//核心,实质就是调用了ViewRootImpl.dispatchInvalidateDelayed方法
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
public void handleMessage(Message msg) {
......
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
......
}
......
}
invalidate与postInvalidate方法总结
invalidate系列方法请求重绘View树时,如果View【大小】没有发生变化就不会调用layout过程,并且只绘制那些"需要重绘的"View,也就是哪个View请求invalidate系列方法,就绘制该View。
常见的引起invalidate方法操作的原因主要有:
- 直接调用invalidate方法,请求重新draw,但只会绘制调用者本身。
- 触发setSelection方法,请求重新draw,但只会绘制调用者本身。
- 触发setEnabled方法,请求重新draw,但不会重新绘制任何View包括该调用者本身。
- 触发requestFocus方法,请求View树的draw过程,只绘制"需要重绘"的View。
- 触发setVisibility方法, 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要"重新绘制"的视图。
补充performTraversals方法调用时机
之前我们说过:整个View绘制流程的最初代码是在ViewRootImpl类的performTraversals()方法中开始的,上面当时只是告诉你了这个结论,至于这个ViewRootImpl类的performTraversals()方法为何会被触发没有说明原因,现在我们就来分析一下这个触发的源头。
我们先来看下之前博文中分析过的,Activity中setContentView方法所调用的PhoneWindow中的setContentView方法的源码:
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
......
//如果mContentParent为空进行一些初始化
if (mContentParent == null) {
installDecor();
}
......
//把我们的view追加到mContentParent
mContentParent.addView(view, params);
......
}
public void addView(View child) {
addView(child, -1);
}
public void addView(View child, int index) {
......
addView(child, index, params);
}
public void addView(View child, int index, LayoutParams params) {
......
//该方法稍后后面会详细分析
requestLayout();
//重点关注!!!
invalidate(true);
......
}
requestLayout方法源码分析
和invalidate类似,之前在分析View绘制流程时或多或少都调用到了这个方法,而且这个方法对于View来说也比较重要,所以我们接下来分析一下他。如下为View的requestLayout源码:
public void requestLayout() {
......
if (mParent != null && !mParent.isLayoutRequested()) {
//从这个View开始向上一直requestLayout,最终到达ViewRootImpl的requestLayout
mParent.requestLayout();
}
......
}
如下就是ViewRootImpl的requestLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//View调用requestLayout最终层层上传到ViewRootImpl后最终触发了该方法
scheduleTraversals();
}
}
requestLayout方法总结
对于requestLayout方法来说,总结如下:
- requestLayout()方法会调用【measure】过程和【layout】过程,不会调用【draw】过程,所以不会重新绘制任何View(包括该调用者本身)。
- 使用条件:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求父view重新调用他的onMeasure、onLayout来重新设置自己位置。
- 用途:有时我们在改变一个view 的内容之后,可能会造成显示出现错误,比如写ListView的时候 重用convertview中的某个TextView 可能因为前后填入的text长度不同而造成显示出错,此时我们可以在改变内容之后调用requestLayout方法加以解决。
Google文档的英文说明:
Call this when something has changed which has invalidated the layout of this view 当View的布局已经无效时调用. This will schedule重新安排 a layout pass of通过 the view tree. This should not be called while the view hierarchy层次 is currently in a layout pass (isInLayout(). If layout is happening, the request may be honored at the end of the current layout pass (and then layout will run again) or after the current frame is drawn and the next layout occurs.
Subclasses子类 which override this method should call the superclass method to handle possible request-during-layout errors correctly.
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/2a3fccd829120089d24547929175ae29.html