Android进阶知识(十二):View的工作原理之基本概念
从这一篇章开始,笔者将介绍关于View的工作原理与自定义View的相关内容。在介绍View的工作原理(三大流程)之前,读者有必要了解一些基本概念。
一、Activity的视图层结构
在ActivityThread中,当Activity对象被创建完毕之后,会将DecorView添加到Window中。Window是一个抽象类,其唯一实现类为PhoneWindow,PhoneWindow将一个DecorView设置为整个应用窗口的顶级View。
DecorView作为窗口界面的顶级View,封装了一些操作窗口的基本方法,其继承自FrameLayout(说明DecorView是一个ViewGroup),View层的事件都是先经过DecorView,然后才传递给View。值得一提的是,在Activity中通过setContentView设置的布局文件其实是被添加到id为content的内容栏中。
二、ViewRoot
ViewRoot对应于ViewRootImpl类,其为连接WindowManager和DecorView的纽带,View的三大流程均是ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕之后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联(至于这些关联关系,笔者后续将单独做一篇介绍)。
View的绘制流程是从ViewRoot的performTraversals方法开始的,其经过measure、layout和draw三个过程才能最终将一个view绘制出来。performTraversals的工作流程如下所示。
performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程。其中在performMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有子元素进行measure过程,这就把measure过程传递给子元素中。performLayout和performDraw的传递流程和performMeasure类似,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的(具体的三大流程过程见见:Android进阶知识(十三):View的工作流程之measure过程与Android进阶知识(十四):View的工作流程之Layout过程和Draw过程)。
Measure过程决定了View测量的宽/高,几乎所有情况下测量后的宽/高等同于View最终的宽高。Layout过程决定了View四个顶点的坐标和实际的View的宽/高,可通过getWith和getHeight获取最终宽/高。Draw过程决定了View的显示,只有draw方法完成以后View的内容才能呈现。
三、LayoutParams
LayoutParams是布局参数类,其指定了视图View的高度和宽度等布局参数,可以通过以下参数指定。
参数 | 解释 |
具体值 | dp/px |
fill_parent | 强制性使子视图的大小扩展至与父视图大小相等(不含padding) |
match_parent | 与fill_parent相同,用于Android 2.3之后版本 |
wrap_content | 自适应大小,强制性地使视图扩展以便显示其全部内容(含padding) |
四、理解MeasureSpec
为了能够理解View的measure过程,对于参与View测量过程的MeasureSpec必须要有所了解,MeasureSpec在很大程度上决定了一个View的尺寸规格。
一个View的MeasureSpec一旦确定之后,onMeasure中就可以确定View的测量宽/高。
- MeasureSpec
MeasureSpec代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)。
MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配。其中SpecMode 有三类,每一类都表示特殊的含义,如下所示。
模式 | 具体描述 | 应用场景 |
UNSPECIFIED | 父容器不对View有任何限制(即View可取任意大小) | 系统内部,表示一种测量的状态 |
EXACTLY | 父容器已经检测出View所需的精确大小,View的最终大小就是SpecSize所指定的值 | 对应于LayoutParams中的match_parent和具体数值这两种模式 |
AT_MOST | 父容器指定一个可用大小即SpecSize,View的大小不能大于这个值 | 对应于LayoutParams中的wrap_content |
- MeasureSpec和LayoutParams的对应关系
上面提到了,MeasureSpec很大程度上决定了View的尺寸规格,这里之所以说很大程度,原因在于View的尺寸还和LayoutParams有关。
对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定。
根据ViewRootImpl的源码,DecorView的MeasureSpec的产生过程比较明确,根据它的LayoutParams中的宽/高来划分如下表所示。
LayoutParams | DecorView的MeasureSpec |
MATCH_PARENT | EXACTLY + 窗口大小 |
WRAP_CONTENT | AT_MOST + 大小不定,但不能超过窗口大小 |
固定大小(比如100dp) | EXACTLY + LayoutParams中指定大小 |
而对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。
根据ViewGroup的相关源码,可以得到普通View的MeasureSpec的创建规则,如下表所示,其中表中的paresentSize是指父容器中目前可使用大小(除去padding和margin)。
parentSpecMode \ childLayoutParams | EXACTLY | AT_MOST | UNSPECIFIED |
dp/px | EXACTLY + childSize | EXACTLY + childSize | EXACTLY + childSize |
match_parent | EXACTLY + parentSize | AT_MOST + parentSize | UNSPECIFIED + 0 |
wrap_content | AT_MOST + parentSize | AT_MOST + parentSize | UNSPECIFIED + 0 |
参考资料:《Android开发艺术探索》
自定义View Measure过程 - 最易懂的自定义View原理系列(2)