天天看点

Android requestLayout 和 invalidata , postInvalidate 比较

<code>invalidate</code> 在UI线程中使用。

<code>postInvalidate</code> 在非UI线程中通知重绘。

View 确定自身已经不适合现有区域时,调用<code>requestLayout()</code>,通知父View重新测量和绘制此View的位置。 

当View的LayoutParams发生改变时,也应该调用这个方法。

当调用requestLayout 时,会逐层向上进行传递,直到ViewRootImpl进行处理,如果子view调用了这个方法,会通知View树重新进行一次测量,布局,绘制的过程.

在requestLayout方法中,首先先判断当前View树是否正在布局流程,接着为当前子View设置标记位,该标记位的作用就是标记了当前的View是需要进行重新布局的,接着调用<code>mParent.requestLayout</code>方法,这个十分重要,因为这里是向父容器请求布局,即调用父容器的<code>requestLayout</code>方法,为父容器添加<code>PFLAG_FORCE_LAYOUT</code>标记位,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到<code>DecorView</code>,即根View,而根View又会传递给<code>ViewRootImpl</code>,也即是说子View的<code>requestLayou</code>t事件,最终会被<code>ViewRootImpl</code>接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有<code>ViewRootImpl</code>能够处理<code>requestLayout</code>事件。

在ViewRootImpl中,重写了<code>RequestLayout()</code>

在这里,调用了scheduleTraversals方法,这个方法是一个异步方法,最终会调用到ViewRootImpl#performTraversals方法,这也是View工作流程的核心方法,在这个方法内部,分别调用measure、layout、draw方法来进行View的三大工作流程,对于三大工作流程,前几篇文章已经详细讲述了,这里再做一点补充说明。

View# measure()

View# layout()

那么到目前为止,requestLayout的流程便完成了。

小结:子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。

该方法会引起View树的重绘,通常在内部调用或者刷新界面时进行调用

View#invalidate()

可以看到,在该方法内部,先设置当前视图的标记位,接着有一个do…while…循环,该循环的作用主要是不断向上回溯父容器,求得父容器和子View需要重绘的区域的并集(dirty)。当父容器不是ViewRootImpl的时候,调用的是ViewGroup的<code>invalidateChildInParent</code>方法

ViewGroup#invalidateChildInParant()

1

可以看出,这个方法做的工作主要有:调用offset方法,把当前dirty区域的坐标转化为父容器中的坐标,接着调用union方法,把子dirty区域与父容器的区域求并集,换句话说,dirty区域变成父容器区域。最后返回当前视图的父容器,以便进行下一次循环。

回到上面所说的do…while…循环,由于不断向上调用父容器的方法,到最后会调用到ViewRootImpl的invalidateChildInParent方法 

ViewRootImpl#invalidateChildInParent:

2

3

4

5

6

可以看出,该方法所做的工作与上面的差不多,都进行了offset和union对坐标的调整,然后把dirty区域的信息保存在mDirty中,最后调用了scheduleTraversals方法,触发View的工作流程,由于没有添加measure和layout的标记位,因此measure、layout流程不会执行,而是直接从draw流程开始。

好了,现在总结一下invalidate方法,当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。

由以上代码可以看出,只有attachInfo不为null的时候才会继续执行,即只有确保视图被添加到窗口的时候才会通知view树重绘,因为这是一个异步方法,如果在视图还未被添加到窗口就通知重绘的话会出现错误,所以这样要做一下判断。接着调用了ViewRootImpl#dispatchInvalidateDelayed方法:

这里用了Handler,发送了一个异步消息到主线程,显然这里发送的是MSG_INVALIDATE,即通知主线程刷新视图,具体的实现逻辑我们可以看看该mHandler的实现:

可以看出,参数message传递过来的正是View视图的实例,然后直接调用了invalidate方法,然后继续invalidate流程。

    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/6905478.html,如需转载请自行联系原作者

继续阅读