Android进阶知识(十二):View的工作原理之基本概念

  从这一篇章开始,笔者将介绍关于View的工作原理与自定义View的相关内容。在介绍View的工作原理(三大流程)之前,读者有必要了解一些基本概念。

一、Activity的视图层结构

  在ActivityThread中,当Activity对象被创建完毕之后,会将DecorView添加到Window中。Window是一个抽象类,其唯一实现类为PhoneWindow,PhoneWindow将一个DecorView设置为整个应用窗口的顶级View

Android view的显示过程 android view详解_Activity的视图层架构

  DecorView作为窗口界面的顶级View,封装了一些操作窗口的基本方法,其继承自FrameLayout(说明DecorView是一个ViewGroup),View层的事件都是先经过DecorView,然后才传递给View。值得一提的是,在Activity中通过setContentView设置的布局文件其实是被添加到id为content的内容栏中。

Android view的显示过程 android view详解_LayoutParams_02

二、ViewRoot

  ViewRoot对应于ViewRootImpl类,其为连接WindowManager和DecorView的纽带,View的三大流程均是ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕之后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联(至于这些关联关系,笔者后续将单独做一篇介绍)。

  View的绘制流程是从ViewRoot的performTraversals方法开始的,其经过measure、layout和draw三个过程才能最终将一个view绘制出来。performTraversals的工作流程如下所示。

Android view的显示过程 android view详解_Android view的显示过程_03

  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过程)。

Android view的显示过程 android view详解_Activity的视图层架构_04

  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的测量宽/高

Android view的显示过程 android view详解_DecorView_05

  1. MeasureSpec

  MeasureSpec代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)

Android view的显示过程 android view详解_Activity的视图层架构_06

  MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配。其中SpecMode 有三类,每一类都表示特殊的含义,如下所示。

模式

具体描述

应用场景

UNSPECIFIED

父容器不对View有任何限制(即View可取任意大小

系统内部,表示一种测量的状态

EXACTLY

父容器已经检测出View所需的精确大小,View的最终大小就是SpecSize所指定的值

对应于LayoutParams中的match_parent和具体数值这两种模式

AT_MOST

父容器指定一个可用大小即SpecSize,View的大小不能大于这个值

对应于LayoutParams中的wrap_content

  1. MeasureSpec和LayoutParams的对应关系

  上面提到了,MeasureSpec很大程度上决定了View的尺寸规格,这里之所以说很大程度,原因在于View的尺寸还和LayoutParams有关

  对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定

Android view的显示过程 android view详解_DecorView_07

  根据ViewRootImpl的源码,DecorView的MeasureSpec的产生过程比较明确,根据它的LayoutParams中的宽/高来划分如下表所示。

LayoutParams

DecorView的MeasureSpec

MATCH_PARENT

EXACTLY + 窗口大小

WRAP_CONTENT

AT_MOST + 大小不定,但不能超过窗口大小

固定大小(比如100dp)

EXACTLY + LayoutParams中指定大小

  而对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定

Android view的显示过程 android view详解_Activity的视图层架构_08

  根据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)