Android View在进行绘制之前会先进行measure和layout,我们的分析从draw开始并假设上面两步已经完成。View的绘制函数是在ViewRootImpl.java的performDraw()开始,以下就是简单调用流程图:
简单总结:
1.draw(boolean fullRedrawNeeded),参数设置是否全部重新绘制,设置之后的结果就是dirty矩阵重新设置为View最大值。
2.当dirty不为空并且正在动画,则准备开始绘制View。在View绘制之前会先进行canvas设置,主要就是两部分设置屏幕密度和设置分辨率,两个方法均通过native设置。
3.现在开始绘制View,直接调用mView.draw(canvas);在View的draw(canvas)函数中实现了绘制的六个步骤,但是考虑2和5步骤不常用所以做优化处理暂不处理。接下来是绘制的关键步骤说明:
第1步:绘制背景。前提是该View背景是非透明,一般View背景都是非透明。在绘制背景之前,如果scrollX和scrollY存在值,那么先去平移canvas。在默认情况下canvas的原点是View本身的左上角位置,如果存在scrollX和scrollY,说明此时View内容发生了滚动。但是要绘制的内容或者说程序员指定的背景都是相对于View可视区域的原点,所以在绘制之前需要先做canvas平移到View可视区域的处理。然后在canvas绘制背景。
第3步:绘制View本身内容。其实质就是调用onDraw(canvas),但是前提仍然是View不是透明状态。View的该方法未实现。
第4步:ViewGroup绘制子View。首先需要说明的是在这里,这个View实际上是FrameLayout,所以该方法实际是FrameLayout需要实现的。但是我们现在只看ViewGroup最原始的实现方法。主要有如下阶段:
(1)检查是否设置了布局动画是否需要绘制缓存。在for循环中遍历所有子View设置使用的布局动画参数及对象和绘制View内容到Bitmap缓存上。在绘制到Bitmap时,首先依据参数处理缩放,然后计算缓存该View的Bitmap大小,如果过大直接返回,最后创建Bitmap并且设置为canvas绘制的对象。在绘制之前同样需要处理scrollX和scrollY,然后在检查ViewGroup是否要跳过该View的绘制,如果不是在直接执行onDraw(canvas)绘制内容到缓存。最后做动画启动前参数设置回调接口。
(2)剪裁canvas然后逐个绘制子View。首先根据ViewGroup的padding属性剪裁canvas,接下来按照一定顺序(可重写自定义)绘制子View。主要经过如下步骤:
判断是否使用缓存绘制View
获取View的动画对象,执行动画drawAnimation(parent, drawingTime, a, scalingRequired);在绘制动画时进行如下步骤:
1.初始化动画开始前的变量
2.获取变换对象,这个变换对象就是动画执行的依据。
3.执行重绘动画。
接下来做多重判断,最主要的判断就是canvas是否在父View的剪切区:canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW);如果不在就直接返回。
判断是否使用绘制缓存,如果需要使用先去绘制缓存到Bitmap。
接下来就是应用变换对象到canvas,这也是之所以动画形成的主要步骤。首先处理缩放接着是变换对象最后就是透明度。
接下来就是绘制View。如果使用缓存绘制,直接将Bitmap绘制到canvas。否则就回调draw(canvas);绘制。
最后恢复canvas状态。判断是否是一次动画如果结束回调接口,如果动画仍然进行继续调用重绘函数。
(3)处理绘制移除但是存在动画的View。
(4)绘制debug的View。
(5)在重绘子View时如果需要重绘ViewGroup则接着重绘。
(6)判断动画结束,则回调结束接口。
第6步:绘制滚动条。
(1)获取滚动条状态,如果是OFF直接返回,如果是FADING动态改变滚动条透明度同时设置重绘标志。
(2)如果是水平滚动条,以其为例。首先获取其高度及滚动条相对于View的坐标,然后直接调用onDrawHorizontalScrollBar()绘制,其内部就是Drawable对象的绘制。如果是滚动状态需要持续调用重绘。
(4)垂直滚动类似。
至此就是View绘制的主要步骤。