ViewRoot和DecorView
ViewRoot对应ViewRootImpl类, 它是连接WindowManager和DecorView的桥梁, View的measure/layout/draw流程均有ViewRoot发起. 在ActivityThread中, 当Activity对象创建后, 会将DecorView添加到Window中, 同时创建ViewRootImpl对象, 并使它们关联起来.View的绘制从ViewRoot的performTraversals开始, 它经过measure/layout/draw三个过程, 最终被绘制出来.
- measure 测量View的宽高
- layout 确定View在父容器中的位置
- draw 将View绘制在屏幕上
performTraversals中调用measure方法, measure调用onMeasure, 在onMeasure中会对所有子元素进行measure过程, 此时measure流程从父容器传递到了子元素中, 完成一次measure, 子元素重复父容器的过程, 遍历整个View树. performLayout和performDraw也类似, 但performDraw的传递是通过在draw中调用dispatchDraw实现的.
Measure后, 可以通过
getMeasuredWidth
和getMeasuredHeight
获取View测量后的宽高.- MeasureSpec(20990行)
- MesaureSpec
- 代表一个32为int值, 高2位代表SpecMode, 低30位代表SpecSize
- SpecMode 有三种
- UNSPECIFIED 父容器不对View有任何限制, 要多大给多大, 一般用于系统内部.
- EXACTLY 父容器已经检测出了View所需要的精确大小, 其大小就是MeasureSize指定的值. 对应于LayoutParams中match_parent和具体的数值这两种模式
- AT_MOST 父容器指定了一个可用大小即MeasureSize, View的大小不能大于这个值, 对应于LayoutParams中的wrap_content.
- MeasureSpec和LayoutParams的对应关系
MeasureSpec由父容器和LayoutParams一起决定.
- DecorView(1214行)
ViewRootImpl#measureHierarchy
调用ViewRootImpl#getRootMeasureSpec
通过屏幕尺寸和LayoutParams计算出DecorView的MeasureSpec. - 普通View(5939行)
MeasureSpec和父容器的MeasureSpec和子元素本身的LayoutParams有关, 并且还有View的margin和padding.
ViewGroup#measureChildWithMargins
调用ViewGroup#getChildMeasureSpec
计算出普通View的MeasureSpec.
- DecorView(1214行)
- MesaureSpec
View的工作流程
measure过程
View 的measure过程
View#measure
调用View#onMeasure
它又调用View#getDefaultSize
,
如果SpecMode是UNSPECIFIEDmeasuredWidth = (background == null) ? minWidth : max(minWidth, background.getMinimumWidth)
measuredHeight = (background == null) ? minHeight : max(minHeight, background.getMinimumHeight)
如果SpecMode是AT_MOST或EXACTLY
- 宽高就是measureSpec中的specSize
如果直接继承View的自定义控件需要重写onMeasure方法, 并设置wrap_content
时的自身大小, 否则就相当于使用match_parent
原因在于wrap_content
时的SpecMode是AT_MOST, 根据上面的结论, 它实际就是父容器当前剩余的空间大小. 解决办法是给View指定一个默认的内部宽高, 并在wrap_content
时设置此默认的宽高.
ViewGroup的measure过程
除了完成自己的measure过程以外, 还会遍历调用所有子元素的measure方法, 各个子元素再递归去执行这个过程.ViewGroup#measureChildren
会为每一个子元素调用measureChild
所有子元素measure结束后, ViewGroup会将得到的子元素的宽高加上padding, measure自己的宽高.
怎样在Activity启动时就获取一个View的宽高(有例子)
- 在
Activity/View#onWindowFocusChanged
回调里获取 - 使用View.post(Runnable)
- 在ViewTreeObserver 回调中获取
- View.measure 比较复杂
- layout过程
- setFrame设定View的四个顶点位置
- 调用onLayout方法, 确定子元素的位置(View和ViewGroup没有真正实现)
- onLayout调用子元素的layout方法确定自己的位置, 这样完成整个View树的layout过程.
- draw过程
- 绘制背景
- 绘制自己
- 绘制children
- 绘制装饰
- 自定义View
- 分类
- 继承View 重写onDraw
- 继承ViewGroup 派生特殊的Layout
- 继承特定的View
- 继承特定的ViewGroup
- 须知
- 让自定义View 支持wrap_content
- 让自定义View 支持padding
- 使用View#post, 而不是使用Handler
- View中有线程或动画需要在
View#onDetachedFromWindow
时停止 - 处理滑动冲突
- 实例
- 思想
- 分类
4. Android Framework - View的工作原理
最新推荐文章于 2024-04-22 16:56:14 发布