View|工作流程

1,577 阅读6分钟

目录

目录

一、基础知识

在学习View的工作原理之前,需要先学习一些基本的概念。

2.1 ViewRoot

Q1: ViewRoot是什么?

  • 对应ViewRootImpl类。
  • 连接WindowManagerServiceDecorView的纽带。

Q2:ViewRoot在View绘制中有什么作用?

  • View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成。

注意:ViewRoot并不属于View树的一份子。从源码实现上来看,它既非View的子类,也非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图

View的绘制流程从ViewRootperformTraversals开始,如图。

onMeasure方法会对所有子元素进行measure过程,在measure方法中又会调用onMeasure方法,如此反复最终完成整个View树的遍历,layoutdraw方法同理。

performTraversals大致流程

2.2 DecorView

Q1:DecorView是什么?

  • 包括两部分,标题栏和内容栏,如图。

  • DecorViewFrameLayout的子类,它可以被认为是Android视图树的根节点视图。setContentView所布置的文件是被加入内容栏的。

Q: DecorView在View绘制中有什么作用?

View层的事件都需先经过DecorView,再传递给View,分发的过程在View体系详解有讲到。

DecorView组成

2.3 ViewGroup.LayoutParams

此部分参考自:自定义View Measure过程 - 最易懂的自定义View原理系列(2)

Q1:怎么理解ViewGroup.LayoutParams

ViewGroup 的子类(RelativeLayout、LinearLayout)有其对应的 ViewGroup.LayoutParams 子类

如:RelativeLayoutViewGroup.LayoutParams子类 = RelativeLayoutParams

Q2: 这个类有什么作用?

指定视图View 的高度(height) 和 宽度(width)等布局参数。

Q3:怎么使用?

参数解释
具体值dp / px
fill_parent强制性使子视图的大小扩展至与父视图大小相等(不含 padding )
match_parent与fill_parent相同,用于Android 2.3 & 之后版本
wrap_content自适应大小,强制性地使视图扩展以便显示其全部内容(含 padding )

2.4 MeasureSpec

  • 定义:测量规格类。

  • 组成:测量规格(MeasureSpec) = 测量模式(mode)(高2位) + 测量大小(size)(低30位)。

  • 作用:通过宽测量值widthMeasureSpec和高测量值heightMeasureSpec决定View的大小。

Q1:为什么说是很大程度决定了View的尺寸规格?

答:View的尺寸规格还受父容器影响,因为父容器影响View的MeasureSpec的创建过程。

Q2:MeasureSpec有几种模式?

测量模式(Mode)的类型有3种:UNSPECIFIED、EXACTLY 和 AT_MOST。

MeasureSpec的三种模式

2.4.1 MeasureSpec值的计算

  • View: 取决于View的布局参数(LayoutParams)和父容器的MeasureSpec值。

  • 顶级View: 取决于自身布局参数 和窗口尺寸。

View

顶级View

计算总结

二、View的工作流程

以下流程图的方法为源码中的方法,感兴趣的读者可以自行查看源码,强推Carson_Ho博客,内有详细解读。

2.1 measure

作用:测量View的宽/高。

注意:某些情况需要多次measure才能确定View的宽高,此时测试的结果不准确,建议在layout过程中onLayout()获取最终宽/高。

measure测量有两种情况:

  • 单一View
  • ViewGroup

measure测量的两种情况

2.1.1 View

说明:

Measure过程中,主要目的就是为了测量出View的宽/高,在measure()入口方法中调用了onMeasure(),而在onMeasure方法中,调用了getDefaultSize()得出测量后View的宽/高,再调用setMeasureDimension()存储测量后的宽高。测量过程到此结束,结果是存储了一个测量后的宽高。

Q1:measure流程最后使用的是getDefaultSize()得出的宽/高,那么这个宽/高是什么?

  • AT_MOST和EXACTLY:

    getDefaultSize()返回的大小是measureSpec中的specSize,这个specSize就是最终的测量结果。

  • UNSPECIFIED:

    • 有背景

      宽/高为android:minWidth属性所指定的值,若无指定,则为0。

    • 无背景

      View的宽/高度为android:minWidth/android:minHeight属性所指定的值和mBackground.getMinimumWidth()/mBackground.getMinimumHeight()中的最大值。

    测量结果

2.1.2 ViewGroup

  • ViewGroup除了完成自己的measure过程之外,还会遍历所有子View的measure方法。

  • ViewGroup是一个抽象类,没有重写View的onMeasure方法(自定义View时需要自己实现)。

  • 提供了一个measureChildren方法。

说明:在ViewGroupmeasure过程中,先在入口measure()内调用onMeasure(),与View中的onMeasure不同,ViewGroup中没有实现这个方法,因为不同的ViewGroup子类(LinearLayoutRelativeLayout / 自定义ViewGroup子类等)具备不同的布局特性,这导致他们子View的测量方法各有不同,故需自己重写。

在这个方法中包含了三个方法

  • measureChildren()

系统方法,遍历子View 并且调用measureChild()进行下一步测量。

  • measureCarson()

需要自己重写,合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值。

  • setMeasureDimension()

与单一View一样,存储测量后的数据。

2.2 layout

作用:确定View的位置。

两种情况

2.2.1 View

View的layout过程

说明:

  • 由于单一View是没有子View的,故onLayout()是一个空实现。

  • 由于在layout()中已经对自身View进行了位置计算,所以单一View的layout过程在layout()后就已完成了。

2.2.2 ViewGroup

ViewGroup的layout过程

说明:ViewgrouponLayout方法中遍历了子View,调用child.layout(),计算每个子View的位置,一开始计算ViewGroup位置时,调用的是ViewGrouplayout()onLayout(),当遍历子View计算子View位置时,调用的是子View的layout()onLayout()

2.3 draw

作用:将View绘制到屏幕上面。

2.3.1 View

说明:所有的视图最终都是调用 View 的 draw ()绘制视图。

2.3.1 ViewGroup

ViewGroup的draw过程

三、自定义View

3.1 自定义View的分类

  • 继承View重写onDraw方法
    • 主要用于实现一些不规则的效果。
    • 重写onDraw方法,需要支持wrap_content并且需要自己处理padding。
  • 继承ViewGoup派生特殊的Layout(除了系统布局,重新定义一种新布局)
    • 实现自定义布局。
    • 需要合适的处理ViewGroup的测量,布局两个过程,并同时处理子元素的测量和布局过程。
  • 继承特定的View
    • 扩展某些已有的View的功能。
    • 容易实现,不需要自己支持wrap_content和padding。
  • 继承特定的ViewGroup
    • 常见,第二种情况实现的也能用这种方式实现。
    • 与第二种的区别就是不用自己处理ViewGroup的测量和布局,第二种更接近底层。

3.2 自定义View须知

  • 让View支持wrap_content
  • 让View支持padding
  • 不要在View中使用Handler
    • View内部本身有post系列的方法,可以完全替代Handler的作用。
  • View中如果有线程或动画,需要及时停止
    • 与生命周期同步,不然会造成内存泄漏
  • 处理滑动冲突

参考自: