一、invalidate和postInvalidate
这两个方法都是在重绘当前控件的时候调用的。invalidate在UI线程中调用,postInvalidate在非UI线程中调用。因为android的UI线程是非线程安全的,所以在非UI线程中,需要使用postInvalidate来使View重绘。view调用invalidate将导致当前view的重绘(draw调用),view的父类将不会执行draw方法;viewGroup调用invalidate会使viewGroup的子view调用draw,也就是viewGroup内部的子view进行重绘;
invalidate方法用于在UI线程中请求重绘视图。当我们希望在主线程中更新UI时,可以在UI线程中直接调用invalidate()方法。
内部实现:调用了invalidate方法后,为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,开始View树重绘流程(只绘制需要重绘的视图)
To farce a view to draw,call invalidate().——摘自View类源码
从上面这句话看出,invalidate方法会执行draw过程,重绘View树。
当View的appearance发生改变,比如状态改变(enable,focus),背景改变,隐显改变等,这些都属于appearance范畴,都会引起invalidate操作。
所以当我们改变了View的appearance,需要更新界面显示,就可以直接调用invalidate方法。
View(非容器类)调用invalidate方法只会重绘自身,ViewGroup调用则会重绘整个View树。
postInvalidate方法用于在非UI线程中请求重绘视图。如果我们希望在非UI线程中更新UI,就需要使用postInvalidate()方法。
内部实现:当调用postInvalidate()方法时,系统会通过Handler将重绘任务添加到主线程的消息队列中。在UI线程的消息循环中,当处理到该消息时,会触发invalidate()方法来进行实际的重绘操作。这样可以确保在非UI线程中请求的重绘操作在主线程中执行,避免多线程并发导致的问题。
二、requestLayout
requestLayout方法只会导致当前view的measure和layout,而draw不一定被执行,只有当view的位置发生改变才会执行draw方法,因此如果要使当前view重绘需要调用invalidate。
requestLayout方法用于请求重新布局视图。当我们希望更新视图的尺寸或位置时,需要调用requestLayout()方法。该方法会触发视图层次结构的重新测量、布局和绘制过程。例如,当修改了视图的宽度、高度、位置或其他布局参数时,应调用requestLayout()方法来请求重新布局。
内部实现:调用requestLayout方法,标记当前View及父容器,requestLayout()事件逐层向上传递,直到DecorView,DecorView传递给ViewRootImpl,ViewRootImpl#requestLayout()接收并处理该事件【最终走到这个方法ViewRootImpl.scheduleTraversals()】。ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View进行测量onMeasure、布局onLayout、绘制onDraw。
To initiate a layout, call requestLayout(). This method is typically called by a view on itself when it believes that it can no longer fit within its current bounds.——摘自View源码
从上面这句话看出,当View的边界,也可以理解为View的宽高,发生了变化,不再适合现在的区域,可以调用requestLayout方法重新对View布局。
View执行requestLayout方法,会向上递归到顶级父View中,再执行这个顶级父View的requestLayout,所以其他View的onMeasure,onLayout也可能会被调用。
invalidate和requestLaoyout区别一张图告诉你
**`invalidate()`**
- 当视图的外观(如颜色、图形、图像等)发生改变,但不涉及视图尺寸或位置的变化时,应当调用 `invalidate()` 方法。
- 这个方法会导致视图的部分或全部区域被标记为“dirty”(无效),随后系统会在下一个绘图周期中调用 `onDraw()` 方法重绘视图内容。
**`requestLayout()`**
- 当视图的大小或者位置需要根据内部状态或外部条件重新计算时,需要调用 `requestLayout()` 方法。
- 调用此方法不仅会导致当前视图重新布局,还会递归地通知所有父视图它们也需要重新布局,从而引发整个视图树从上至下进行布局计算,这包括 measure 和 layout 两个阶段。
- 常见应用场景有:视图的尺寸因为内容增加而需要扩大,或者视图的位置由于约束条件变化而需要移动等。
总结一下:
- 如果只是视图内容发生了变化,不影响视图尺寸和位置,使用 `invalidate()`。
- 如果视图的尺寸或位置需要改变,则应调用 `requestLayout()`,同时,由于布局变更通常也会引起内容变化,所以调用 `requestLayout()` 后系统一般会自动跟进 `invalidate()` 更新显示内容。
三、onLayout
在很多情况下requestLayout是不需要被调用的。例如,我们把一个AbsoluteLayout里面的childView挪动一下位置。我们仅仅需要调用的可能就是重新布局当前AbsoluteLayout,然后调用invalidate方法进行重绘。而不是从当前View向上的整个View树形结构都要重新layout,onLayout,measure,onMeasure一次。在这种情况下可以直接调用onLayout。然后调用invalidate进行重绘。很明显可以提升绘制效率。由于父View的layout实现中对会通知布局的listener。但是由于无法得到listener,因此调用onlayout的时候无法对其进行通知,这也是这种实现的缺陷。
-------
只要调用addView、setVisbility、setText方法,就会重新调用 requestLayout,重新执行 view的绘制流程,requestLayout在onMeasure之后调用;
这种重新绘制和第一次view绘制不一样,比如一次性添加10个TextView,它会等你全部添加完后才会执行一遍onMeasure,不可能是添加1个TextView就调用一次onMeasure
好像用来刷新界面的,因为onMeasure绘制完成之后调用requestLayout,也只有刷新了。
requestLayout():view调用这个方法要求parent view重新进行一次测量、布局、绘制这三个流程来更新自己位置。
invalidate():view调用这个方法迫使view进行重新绘制。
一句话,requestLayout()的效果是重新布局自己在父布局中的位置,invalidate()的效果是强制调用自己的onDraw()方法。
整个Android的UI绘制机制是从哪里开始的即入口在哪里呢?答案就是ViewRootImpl类的performTraversals()方法。在这个方法内部,分别调用measure、layout、draw方法来进行View的三大工作流程。
至此,我们就能明白了,requestLayout()会牵动出整个Android绘制机制重新走一遍流程
Invalidate这个方法只能在UI线程中去调用
postInvalidate是可以在非UI线程中去调用刷新UI的
差异点:
1、invalidate方法用于在主线程中请求重绘视图,postInvalidate方法可以再非UI线程中调用
2、invalidate和postInvalidate方法只会触发onDraw方法,requestLayout会触发onMeasure和onLayout方法,当view的位置发生改变才会执行onDraw方法,因此如果要使当前view重绘需要调用invalidate。
疑问:
requestLayout()何时不会触发onDraw()?
如果没有改变控件的left\right\top\bottom就不会触发onDraw()
invalidate()在什么情况下不会触发onDraw?
在ViewGroup中,invalidate默认不重新绘制子view。
如何让ViewGroup在invalidate时会触发onDraw?
本质需要将ViewGroup的dirtyOpaque设置为false
1.在构造函数中调用setWillNotDraw(false);
2.给ViewGroup设置背景。调用setBackground。
总结:
View绘制分三个步骤,顺序是:onMeasure,onLayout,onDraw。经代码亲测,log输出显示:调用invalidate方法只会执行onDraw方法;调用requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。
所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。