1. 概述
关于View的工作原理,一直觉得很重要,配合上一篇的View的事件体系,就可以大致知道整个View的流程。所以这篇就来做一下View的工作原理的学习笔记。
2. ViewRoot和DecorView
在View之前,首先来看下ViewRoot和DecorView的概念。
第一个是ViewRoot,它对应于ViewRootImpl类,它是链接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout、和draw三个过程,最终将一个View绘制出来,其中measure用来测量View的宽和高,layout用来确定View在父容器中的位置,而draw则负责将View绘制在屏幕上。大致的流程如下:
如上图所示,performTraversal会一次调用performMeasure、performLayout、performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw三大流程,其中在performMeasure中会调用measure方法,在measure中又会调用onMeasure方法。在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这就完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。同理,performLayout和performDraw的传递流程类似,不同的是performDraw的传递过程实在draw方法中通过dispatchDraw来实现的,不过这并没有本质区别。
measure过程决定了View的宽高,Measure完成之后,可以通过getMeasuredWidth和getMeasuredHeight来获取View测量后的宽高,Layout过程决定了View的四个顶点的坐标和实际的View的宽高,完成以后可以通过getTop、getBottom、getLeft和getRight来获取View的四个顶点的位置,并且可以通过getWidth和getHeight方法来拿到View的最终宽高。而Draw过程决定了View的显示,只有draw方法完成以后,View的内容才会显示到屏幕上。
以上是ViewRoot的一些概念,接下来看一下DecorView,它实际上继承自FrameLayout:
![98CD0C1E-AE8F-4D3E-8B77-4F256B5C96DD](https://ws4.sinaimg.cn/large/006tNc79gy1flbxtduf83j30bq0dcgls.jpg)
大致示意图如上,作为一个顶级的View,一般情况下它内部会包含有一个竖直方向的LinearLayout,在这个LinearLayout中有上下两部分,如图所示分别是标题栏和内容栏。在Activity中我们通过setContentView方法设置的layout布局实际上就是被添加到了id为content的FrameLayout中。可以通过ViewGroup content = findViewById(R.android.id.content)
来获取到content,通过content.getChildAt(0)
来获取到我们设置的View。
3. MeasureSpec
MeasureSpec是View中的一个内部类,它代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode指测量模式,二SpecSize则是指在某种测量模式下的规格大小。MeasureSpec的一些常量定义如下:
|
|
MeasureSpec通过奖SpecMode和SpecSize打包成一个int值来避免过多的内存分配,并提供了打包和解包方法。
从上面的代码中可以看出SpecMode一共有三种类型:
UNSPECIFIED
父容器不对View有任何限制,要多大有多大,这种情况一般用于系统内部,表示一种测量状态。
EXACTLY
父容器已经给出了View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的match_parent和具体数值这两种模式。
AT_MOST
父容器制定了一个可用大小即SpecSize,View的大小不能大雨这个值,具体什么要看不同View的具体实现,这个对应于LayoutParams中的wrap_content。
4. MeasureSpec和LayoutParams的对应关系
在给View设置了LayoutParams之后,在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。LayoutParams和父容器一起决定了View的MeasureSpec,另外对于顶级View(DecorView)和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,MeasureSpec由窗口的尺寸和自身的LayoutParams来共同决定,对于普通的View来说,MeasureSpec由夫荣妻的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定之后,onMeasure中就可以确定View动测量宽高。
看一下DecorView中的getRootMeasureSpec方法:
|
|
从中可以看出DecorView的MeasureSpec的产生过程,具体规则如下:
- LayoutParams.MATCH_PARENT: 精确模式,大小就是窗口大小
- LayoutParams.WRAP_CONTENT: 最大模式,大小不定,但是不能超过窗口的大小
- 固定大小(比如100dp):精确模式,大小为LayoutParams中制定的大小
对于普通View来说,View的measure过程由ViewGroup传递而来,先看ViewGroup的measureChildWithMargins方法:
|
|
上述方法会对子元素进行measure,在调用子元素的measure方法之前会先通过getChildMeasureSpec方法来获取子元素的MeasureSpec。可以看出,子元素的MeasureSpec的创建和父容器的MeasureSpec和自身的LayoutParams有关,此外还和View的margin以及padding有关。具体如方法getChildMeasureSpec所示:
|
|
上述方法主要是根据父容器的MeasureSpec同世结合View本身的LayoutParams来确定子元素的MeasureSpec。具体逻辑如下图所示: