Android View 的工作原理(包含对 DecorView 和 ViewRoot 的简单介绍)

  • 什么是 View ?

      View 是 Android 中所有控件的基类,View 可以是单个控件,也可以是由多个控件组成的一组控件。ViewGroup 里面可以有子 View,子 View 里面也可以有 ViewGroup。

  • 什么是 ViewRoot、DecorView ?

      View 有三大流程,measure、layout、draw,了解并熟悉其三大流程对于我们进行 Android 开发有着极其重要的作用。在熟悉三大流程之前,先介绍一下 ViewRoot 和 DecorView 的概念。

      Activity 内部 是组合了一个 Window 对象,Activity 所有的事件都是交给 Window 来处理的,但是 Window 是一个抽象类,处理事务具体的操作就只能交给它的实现类,也就是 PhoneWindow,PhoneWindow 就会把事件传给这个 DecorWindow,这个 DecorWinow 是应用窗口的根容器,也就是个顶级 View,其实是个 FrameLayout。这个根容器一般情况下都只有唯一一个子 View ,就是一个垂直的 LinearLayout,上下两部分分别是标题栏和内容栏。而 ViewRoot 是 View 树的管理者,它的成员变量 mView 对应的就是一个布局的根 View,ViewRoot 是 View 和 WindowManger 的桥梁,也就是 DecorView 和 WindowManger 的纽带,对应的是 ViewRootImpl 类。当 Activity 创建完成后,就会创建 ViewRootImpl 对象,将 DecorView 添加到 Window 中,并将 DecorView 和 ViewRootImpl 建立关联。

      View 的三大流程就是从 ViewRoot 的 performTraversals 方法开始的。该方法会依次调用 performMeasure、performLayout、performDraw 三个方法,来完成顶级 View 的三大流程。三个方法又会分别调用 onMeasure、onLayout、onDraw 方法来完成对子 View 的 measure、layout、draw。接着子元素会重复父容器的 measure、layout、draw。来完成整个 View 树的三大流程。measure 决定 View 的 宽高、layout 确定四个顶点的位置、draw 则会将内容呈现在屏幕上。

  • 什么是 MeasureSpec ?

      为了更好的了解 View 的测量过程,我们还需要了解 MeasureSpec。MeasureSpec 在很大程度上决定了一个 View 的尺寸规格。如果对于 DecorView,其 View 的 MeasureSpec 就是由窗口的大小和自身的LayoutParams 所决定的,但是如果是一个子 View,其 MeasureSpec 则是由父容器的 MeasureSpec 和自身的 LayoutParams 所共同决定的。

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

      SpecMode 有三类:UNSPECIFIED、EXACTLY、AT_MOST。

      UNSPECIFIED:父容器不对子 View 做限制,想要多大就多大。

      EXACTLY:父容器检测出 View 的精确大小,这个时候 View 的最终大小就是 SpecSize 所指定的值。对应的是 LayoutParams 中的 match_parent 或 具体数值。

      AT_MOST:父容器指定一个大小 SpecSize,子 View 的大小按照自身要求决定,但是不能超过 SpecSize,对应 LayoutParams 中的 wrap_content。

  • View 的三大流程

    measure 过程:

      measure 过程要分清情况,当要测量的只是一个原始的 View 时,那么只通过 measure 就可以完成其测量过程,但是如果是一个 ViewGroup,就需要遍历测量所有的子 View。
      下面是 View 的 onMeasure 方法:


      它调用了 setMaasureDimension() 方法来设置 View 宽高的测量值,那么 getDefaSize() 方法又是干嘛的呢?


      它是通过测量模式来返回测量得出的值。当测量模式为 UNSPECIFIED 时,测量值就为 result,也就是 getSuggestedMinimumWidth() 或者 getSuggestedMinimumHeight() 返回的值。当测量模式为 AT_MOST 和 EXACTLY 时,返回的大小就是 MeasureSpec 中测量得到的 SpecSize。

      那么 getSuggestedMinimumWidth() 和 getSuggestedMinimumHeight() 方法又在干什么呢?



      这两个方法会判断当前 View 的背景是不是为空,为空就返回 mMinWidth/mMinHeight(即为 minWidth 和 minHeight 属性设置的值),不为空就返回 mMinWidth/mMinHeight 和 背景的 getMinimumWidth()/getMinimumHeight() 返回值中的较大的那个值。

      而 ViewGroup 的 measure 过程如下:

      对于 ViewGroup 来说,不仅需要测量自身,还需要测量所有的子 View。和 View 不同的是,ViewGroup 是一个抽象类,所以没有重写 View 的 onMeasure() 方法,而是提供了 measureChildren() 的方法。


     该方法会遍历 ViewGroup 的每一个 Child,然后调用 measureChild() 方法。


      该方法会得到子元素的 LayoutParams,再通过 getChildMeasureSpec 来获取子元素的 MeasureSpec,然后调用 View 的 measure() 方法进行测量。

    layout 过程:

      layout 过程是为了确定 View 的位置,在 ViewGroup 位置被确定后,它会遍历并确定所有子元素的位置。

      下面是 View 的 layout() 方法:


      

      layout() 方法首先会通过 setFrame() 方法来设定四个顶点的位置,四个顶点一旦确定,View 在父容器中的位置也就确定了,接着会调用 onLayout() 方法。这个方法是父容器确定子元素的位置,因为布局选择的不同,确认的方式也会不一样,所以在 View 和 ViewGroup 中都没有真正实现 onLayout()。具体的 onLayout() 实现要看选择的布局方式。

      ViewGroup 中,会使用 onLayout() 方法遍历所有子元素并调用其 layout() 方法,在 layout() 方法中又会调用 onLayout() 方法,直到确定完整个 View 树的位置。

    draw 过程:

      draw 过程是将 View 绘制到屏幕上,View 源码中的 draw() 方法比较长,就不复制到这了,感兴趣的小伙伴可以自己去看看。

      draw() 方法主要有四个部分:绘制背景、绘制自己、绘制 children、绘制装饰。

      绘制过程的传递是通过 dispatchDraw() 方法来实现的,会遍历所有子元素的 draw() 方法,一层一层绘制完整个 View 树。

      View 还有一个特殊方法,setWillNotDraw(),如下:


      表示如果 View 不需要绘制任何东西时,会将 flags 设置为 true,然后进行相应的优化。默认情况下,View 没有启用这个优化标志位,但是 ViewGroup 默认启用了。当一个自定义 View 继承自 ViewGroup 并且本身不需要绘制时,可以开启这个标志位方便后续优化,如果需要绘制,就得显性关闭 WILL_NOT_DRAW 这个标志位。

学习自《Android 开发艺术探索》



猜你喜欢

转载自blog.csdn.net/young_time/article/details/80266947