作为一名开发者,UI可能是我们Android的启蒙,能在手机上看到自己的代码变成了动态的界面,这种感觉是我们初期学习前进的动力,Android的UI简单、容易上手,而且足够漂亮,一段文字,一张图片,一个列表每实现一个组件,我们好像就掌握了『创造』的方式,而创造是我们乐趣来源的根源,但是当我们都『学会』了这些之后,我们该去做点什么呢?去改写新的代码?去创造新的UI?怎么能把自己脑中的图片画到代码中呢?首先,我们要明白它『UI』是怎么出现的。
妙笔生花
相信有一定开发经验的同学一定知道onDraw()方法,这是我们在实现自定义UI时必定要重写的方法之一,同时也能说出onLayout()和OnMeasure()方法,一个是布局、一个是计算,那么是谁调用了这三个方法呢?再上一层又是谁呢?绘制调用的源头又在哪里?
很多人都会去做源码分析,阅读源码是学习的一个必要步骤,但是我觉得并不适合在博客中去写,大段的代码会阻碍文字的可读性、易读性,这里我们以流程和思想为主,对绘制的流程做一个梳理,读完之后有那个思想了,看起来也会事半功倍。
首先,我们从onDraw(Canvas canvas)开始,我们在写自定义View时,通常会重写这个方法,这也是Android为我们预留的最简单的易用的方法,我们追寻它的上层会发现是draw(Canvas canvas)方法在调用它,这个方法里面有很明显的注释:
Draw traversal performs several drawing steps which must be executed
in the appropriate order:
- Draw the background
drawBackground(canvas)- If necessary, save the canvas' layers to prepare for fading
canvas.getSaveCount();//这里其实有一大段- Draw view's content
onDraw(canvas)- Draw children
dispatchDraw(canvas);- If necessary, draw the fade effect and restore layers
drawAutofilledHighlight(canvas);- Draw decorations (scrollbars for instance)
onDrawForeground(canvas)- Draw the default focus highlight
drawDefaultFocusHighlight(canvas);
可以看出,onDraw只是这个方法中的一个步骤,在onDraw的前后也进行了非常多的操作,通过注释我们能明确draw(Canvas canvas)方法的流程,这里,我们已经能了解一个界面所需的大部分部件,那么它的上层又是什么呢?
通过对代码的追踪,我们找到了ViewRootImpl类,那么问题又来了,这个类又是干什么的?这里我们先不提,等到分析完部分方法,自然就明白它的作用了。在ViewRootImpl中找到draw(Canvas canvas)后发现它的调用流程是这样的: performTraversals() -> performDraw() -> draw(boolean fullRedrawNeeded) -> drawSoftware() -> draw(Canvas canvas),其实我们已经算是找到了半个源头,就是这个performTraversals()方法,为什么不往上找了?因为这里遇到了重要的分支,这里是完成UI绘制三大步的起点,每个View并不是可以凭空的画出来的,我们的代码不是印象派,而是一个精致的蓝图,它需要在正确的位置放置正确的线、面、图形、色彩,而这些工作一个draw是远远不够的,我们还需要Measure。
蓝图毫厘不差
Measure即计算,计算父控件的长、宽、模式(MeasureSpec)计算子控件的长、宽、模式,有了这些才能进行下面的步骤。performMeasure的过程稍显复杂,但是流程与performDraw相差不大。
performTraversals() -> performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) -> measure(int widthMeasureSpec, int heightMeasureSpec) -> setMeasuredDimension(int measuredWidth, int measuredHeight)
其实这些方法并没有做多少事情,只是一个一个的转交(measure做了一些缓存工作),真正的实现其实还是在onMeasure中,最后setMeasuredDimension把长、宽、模式准确的赋值给这个View。
你会发现这个实现并不复杂,但是它真正复杂的并不是在View中而是它的各个子类,我们打开FrameLayout或者LinearLayout,会看到它有大段的重写代码,这才是onMeasure方法的精髓。以FrameLayout为例,我们都知道,FrameLayout是帧布局,要计算它的大小除了计算它自己的大小以外还应该去计算子控件的大小,简单的来说,它要找到最大的子控件从而控制自己的大小。(这里的MeasureSpec我们之后再说,可以先认为是一个Model)而LinearLayout却要计算横轴或者纵轴的长、宽,子控件的累加才是它本身的大小。
有了大小之后,我们就有了精确的部件,如果是一个View已经完成了大部分的工作,但是如果是一个ViewGroup那还有一步需要完成,就是把它摆放到它应有的位置。
布局合纵连横
通过Measure和Draw的分析,你大概已经猜出来了Layout的调用流程,这里就不罗列了,你可以打开源码具体查看一下。
总结
这里用一张图来展示是再合适不过的了。那么,perforTraversals的源头又是什么呢?我们下回再说。
UI绘制流程
练习 聊天界面的复杂布局
聊天Item
网友评论