目录
一、基础知识
在学习View的工作原理之前,需要先学习一些基本的概念。
2.1 ViewRoot
Q1: ViewRoot
是什么?
- 对应
ViewRootImpl
类。- 连接
WindowManagerService
和DecorView
的纽带。
Q2:ViewRoot
在View绘制中有什么作用?
- View的三大流程(测量(
measure
),布局(layout
),绘制(draw
))均通过ViewRoot
来完成。
注意:ViewRoot
并不属于View树的一份子。从源码实现上来看,它既非View的子类,也非View的父类,但是,它实现了ViewParent
接口,这让它可以作为View的名义上的父视图。
View的绘制流程从ViewRoot
的performTraversals
开始,如图。
onMeasure
方法会对所有子元素进行measure
过程,在measure
方法中又会调用onMeasure
方法,如此反复最终完成整个View树的遍历,layout
,draw
方法同理。
2.2 DecorView
Q1:DecorView
是什么?
包括两部分,标题栏和内容栏,如图。
DecorView
是FrameLayout
的子类,它可以被认为是Android视图树的根节点视图。setContentView
所布置的文件是被加入内容栏的。
Q: DecorView
在View绘制中有什么作用?
View层的事件都需先经过
DecorView
,再传递给View,分发的过程在View体系详解有讲到。
2.3 ViewGroup.LayoutParams
此部分参考自:自定义View Measure过程 - 最易懂的自定义View原理系列(2)
Q1:怎么理解ViewGroup.LayoutParams
?
ViewGroup
的子类(RelativeLayout、LinearLayout)
有其对应的ViewGroup.LayoutParams
子类如:
RelativeLayout
的ViewGroup.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。
2.4.1 MeasureSpec
值的计算
View: 取决于View的布局参数(
LayoutParams
)和父容器的MeasureSpec
值。顶级View: 取决于自身布局参数 和窗口尺寸。
二、View的工作流程
以下流程图的方法为源码中的方法,感兴趣的读者可以自行查看源码,强推Carson_Ho博客,内有详细解读。
2.1 measure
作用:测量View的宽/高。
注意:某些情况需要多次
measure
才能确定View的宽高,此时测试的结果不准确,建议在layout
过程中onLayout()
获取最终宽/高。
measure
测量有两种情况:
- 单一
View
ViewGroup
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
方法。
说明:在
ViewGroup
的measure
过程中,先在入口measure()内调用onMeasure()
,与View中的onMeasure
不同,ViewGroup
中没有实现这个方法,因为不同的ViewGroup
子类(LinearLayout
、RelativeLayout
/ 自定义ViewGroup
子类等)具备不同的布局特性,这导致他们子View
的测量方法各有不同,故需自己重写。在这个方法中包含了三个方法
measureChildren()
系统方法,遍历子View 并且调用
measureChild()
进行下一步测量。
measureCarson()
需要自己重写,合并所有子View的尺寸大小,最终得到
ViewGroup
父视图的测量值。
setMeasureDimension()
与单一View一样,存储测量后的数据。
2.2 layout
作用:确定View的位置。
2.2.1 View
说明:
由于单一View是没有子View的,故o
nLayout()
是一个空实现。由于在
layout()
中已经对自身View进行了位置计算,所以单一View的layout过程在layout()
后就已完成了。
2.2.2 ViewGroup
说明:
Viewgroup
在onLayout
方法中遍历了子View,调用child.layout()
,计算每个子View的位置,一开始计算ViewGroup
位置时,调用的是ViewGroup
的layout()
和onLayout()
,当遍历子View计算子View位置时,调用的是子View的layout()
和onLayout()
。
2.3 draw
作用:将View绘制到屏幕上面。
2.3.1 View
说明:所有的视图最终都是调用 View 的 draw ()绘制视图。
2.3.1 ViewGroup
三、自定义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中如果有线程或动画,需要及时停止
- 与生命周期同步,不然会造成内存泄漏
- 处理滑动冲突
参考自:
- 《Android开发艺术探索》
- 《Android进阶之光》
- 自定义View Measure过程 - 最易懂的自定义View原理系列(2)
- 进阶之路 | 奇妙的View之旅