一、invalidate与postInvalidate
invalidate与postInvadlidate都是用于请求View重绘的API,invalidate在主线程中进行调用,而postInvadlidate则在子线程中进行调用。
我们来分析下postInvadlidate的源码 :
[代码]java代码:
1 2 3 |
|
postInvalidate()蒋会调用postInvalidateDelayed(0)方法,继续跟进。
[代码]java代码:
1 2 3 4 5 6 |
|
postInvalidateDelayed方法,通过attachInfo获取到当前的ViewRootImpl对象,调用它的dispatchInvalidateDelayed方法
[代码]java代码:
1 2 3 4 |
|
从上面的源码已经可以看出,postInvalidate的子线程这一个特性了。再继续跟下去看看。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 |
|
代码跟到这里,也就明白了,postInvalidate通过sendMessageDelayed的方法,加入到了looper中,之后在handleMessage中再调用对应View的invalidate()方法,请求View重绘。
二、invalidate流程分析
现在我们来看看invalidate是如何让View进行重绘的呢?
(PS:我这里使用的API版本为23,具体的代码可能和其他的版本有稍许不同)
1、invalidate的请求传递
我们的旅程从View的invalidate传递过程开始
现在来看看View#invalidate()方法。
[代码]java代码:
1 2 3 4 5 6 7 |
|
invalidate调用View#invalidateInternal方法传入当前View的位置参数。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
上述代码中,会判断当前View的状态,是否需要进行重绘,之后设置一系列标记位。通过父View的invalidateChild(this, damage)方法,将需要重绘的区域传递给父View。
接着来看下ViewGroup#invalidateChild方法,这里仅截取了其中的主要代码
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
|
上述代码中,设置了需要重绘的区域dirty。之后再do…while方法中,反复的调用parent = parent.invalidateChildInParent(location, dirty)方法,来调用父类的invalidateChildInParent对View的重绘请求进行传递。这里的parent有可能是ViewGroup,也有可能是ViewRoot,我们先来看看ViewGroup#invalidateChildInParent方法
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
在上述代码中,将会使用offset,把子View需要重绘的坐标区域转换为父View中的坐标区域。之后使用union对子View与父View的区域进行集合运算,获得需要绘制的区域。
接下来我们再来看看ViewRoot#invalidateChildInParent方法,ViewRoot并不是View,ViewRoot的实现类为ViewRootImpl,我们来看下它的invalidateChildInParent方法。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
上述代码中,进入之后会线程以及重绘区域的检查,之后调用invalidateRectOnScreen方法,然后调用scheduleTraversals()方法。
来继续看看ViewRootImpl#scheduleTraversals()。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
上述代码中,将会之后handler,之后会调用mTraversalRunnable类,从而调用doTraversal方法,最后调用performTraversals()执行ViewTree的遍历。
现在继续查看ViewRootImpl#performTraversals()方法。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
在其中进行View的是否可见,是否为surfasce,是否正在绘制,是否存在于删除列表中等判断,之后调用performDraw()开始执行绘制。在performDraw()又调用了ViewRootImpl的draw方法,并传递了fullRedrawNeeded参数,此参数源自mFullRedrawNeeded成员变量,用于表示是否需要重新绘制全部的View。现在继续看看ViewRootImpl#draw源码。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
在draw方法中,根据传如fullRedrawNeeded参数,设置需要重绘的dirty区域,最后调用drawSoftware方法,把参数传递进去,现在继续看ViewRootImpl#drawSoftware源码。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
上述代码中,首先对canvas进行一些属性设置,包括色块、平移等。之后调用mView.draw(canvas)方法,开始对View进行绘制。mView就是window中的顶级视图DecorView(这个坑会在之后的文章中说明,这里当做一个顶级的ViewGroup即可)。
2、绘制流程
DecorView继承自FrameLayout,而ViewGroup的draw方法继承自View,so,所以我们直接看View#draw即可。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
draw方法中,官方对其的步骤进行了清晰的注释,我们来看下流程,在执行流程之前会检查绘制区域是否透明:
* 1、绘制View背景,如果透明则不绘制
* 2、如果需要,则保存画布的图层
* 3、绘制View内容,如果透明则不绘制
* 4、绘制子View————这个很重要
* 5、如果需要,则绘制View的褪色边缘和恢复图层
* 6、绘制装饰滚动条
这里最重要的步骤是第四步,绘制子View,现在我们来看下这个ViewGroup#dispatchDraw(canvas)方法,注意这里的View是一个DecorView,所以要在ViewGroup中去查看这个方法,View中的这个方法是一个空方法。
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
上述代码对所有的子View进行遍历,并调用ViewGroup#drawChild方法。
[代码]java代码:
1 2 3 |
|
drawChild又调用了子View的draw方法,这样绘制就传递了下去,当然这个draw方法和之前这一小节一开始介绍的View#draw方法并不一样,我们来看看
[代码]java代码:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
上述代码会先判断之前是否进行过了绘制,如果没有则进入快速绘制通道,对没有背景的View进行绘制。判断是否需要跳过自身的draw绘制方法,如果跳过则进入dispatchDraw,不跳过则进入当前View的draw方法,即这一小节开头的draw方法,就此形成了循环。同时我们在这里看到了computeScroll()方法,也就印证了上一篇文章对于弹性滑动过程的描述。
三、小结
本文对上一篇遗留的问题postInvalidate与invalidate的区别进行了回答与分析,对invalidate的传递流程,以及View的绘制流程进行了源码分析,解答了invalidate是如何调用computeScroll()的问题。如果在阅读过程中,有任何疑问与问题,欢迎与我联系。
共同学习,写下你的评论
评论加载中...
作者其他优质文章