模型
参考 https://www.cnblogs.com/huansky/p/11911549.html
PhoneWindow —— Window
Activity/Dialog 内部都存在一个 mWindow 对象,Window(抽象类) 全局唯一的实现类为 PhoneWindow
- Activity 中的 mWindow 是在 attach 方法中初始化的
- Dialog 中的 mWindow 是在 构造器中初始化的
WindowManagerImpl —— WindowManager
Android 中使用 WindowManager(接口) 来管理 Window,其实现类为 WindowManagerImpl
其中 WindowManager 继承了 ViewManager 接口,后者声明了 addView,updateViewLayout,removeView
操作 View 的方法。
WindowManagerGlobal
全局单例,WindowManager 的 addView 等方法最终会调用到 WindowManagerGlobal 中
ViewRootImpl
ViewRootImpl 与 DecorView 建立关系的过程
**
handleResumeActivity -> wm.addView(decor) -> WindowManagerImpl#addView -> WindowGlobal#addView -> 初始化 ViewRootImpl -> setView(decorView)
ViewRootImpl 控制 DecorView 递归绘制的过程
requestLayout() -> scheduleTraversals() -> doTraversal() -> performTraversals()
performTraversals() -> | measureHierarchy | |||
---|---|---|---|---|
performMeasure | mView.measure | DecorView#onMeasure | child.measure | |
performLayout | mView.layout | DecorView#onLayout | child.layout | |
performDraw | draw() | drawSoftware | mView.draw |
DecorView —— FrameLayout
如果开发者的 Activity 继承平台的 Activity,则视图树的结构是这样的(默认主题):
继承 AppcompatActivity 视图树要更复杂,感兴趣可以自己查看
DecorView 继承了 FrameLayout,是整个视图树的根节点(它还有个虚拟爹 ViewRootImpl)
调用 Activity 的 setContentView 方法,实际上是把开发者自定义的布局添加到上图 id 为 content 的 FrameLayout 上,这样从 DecorView 到 开发者定义的布局这部分视图树便完整了。
Activity#setContentView() -> PhoneWindow#setContentView() -> PhoneWindow#installDecor -> 初始化 DecorView -> DecorView 与 传入布局关联
ViewGroup/View
ViewGroup/View 绘制主要分为三部分:测量、布局、绘制
核心概念
测量、布局、绘制 分为公共逻辑部分和自定义逻辑部分,前者通过将方法设置为 final 和 @CallSuper 来实现公共逻辑,方法有:measure,layout,draw;后者提供后开发者可重写的方法(onMeasure,onLayout,onDraw)来实现自定义逻辑。
测量
目的 | 确定 View 的尺寸 | |
---|---|---|
MeasureSpec | 32 位 Int 类型,高两位表示的是 SpecMode(测量模式),低 30 位表示的是 SpecSize(测量的具体大小) | UNSPECIFIED AT_MOST EXACTY |
LayoutParam | 子 View 内部保存了其自身在父 View 中的布局参数 | |
View# public final measure (widthMeasureSpec, heightMeasureSpec) | ||
setMeasuredDimension | 调用该方法后便认为完成了自身的测量 protected final 子类只能调用不能重写 | |
布局
目的 | 确定 View 的位置 | |
---|---|---|
View# protected boolean setFrame(int left, int top, int right, int bottom) | 保存最新布局位置信息,该方法内会调用 sizeChange 间接调用 onSizeChanged。 这便是自定义 View 时获取 view 宽高要在 onSizeChanged 的原因,同时也是能够拿到 view 尺寸的时机 | |
getWidth/Height 和 getMeasureWidth/Height 的区别 | getWidth/Height 实际上是在 setFrame() 方法执行完毕才准备好的即 getWidth 和 getHeight 这两个方法可以认为在 layout 执行完成才能调用 这两对值一般情况下是相等的,除非手动调用 layout 方法强制修改布局信息 |
View/ViewGroup 公共逻辑
为了保证公共逻辑的安全性,因此将这部分逻辑抽象为 measure/layout/draw 方法
View# measure(_int widthMeasureSpec, int heightMeasureSpec)_
该方法是 final 的 子类不可重写
View/ViewGroup# layout(_int l, int t, int r, int b)_
ViewGroup 的 layout 是 final 的
View# draw(_Canvas canvas)_
该方法使用 @CallSuper 注解标记,子类如果重写必须调用 spuer.draw()
View/ViewGroup 自定义逻辑
其中 onMeasure 存在系统默认逻辑
View# onMeasure(_int widthMeasureSpec, int heightMeasureSpec)_
_
- 调用子 View measure 方法,让其自我测量,计算子 View 的尺寸
- 调用子 View measure 方法,让其自我测量,计算子 View 的尺寸
- 根据子 View 的给出的尺寸,得出子 View 的位置并保存子 View 的位置和尺寸
- 根据子 View 的给出的尺寸,得出子 View 的位置并保存子 View 的位置和尺寸
- 根据子 View 的尺寸和位置计算自己的尺寸并调用 setMeasuredDimension 保存
- 根据子 View 的尺寸和位置计算自己的尺寸并调用 setMeasuredDimension 保存
View/ViewGroup# onLayout(_boolean changed, int left, int top, int right, int bottom)_
_
- View 中是个空实现 —— View 没有给子 View 布局的需要
- ViewGroup 中被 abstract 修饰 —— 不同 ViewGroup 布局策略不同,因此子类必须重写
View# onDraw(_Canvas canvas)_
空实现
常见问题
- view 不停找 parent 可以一直找到 DecorView,按理说 DecorView 是顶点了,但是 DecorView 还有个虚拟父 view,ViewRootImpl。 ViewRootImpl 不是一个 View 或者ViewGroup,他有个成员 mView 是 DecorView,所有的操作从 ViewRootImpl 开始自上而下分发
- view 的 invalidate 不会导致 ViewRootImpl 的 invalidate 被调用,而是递归调用父 view的invalidateChildInParent,直到 ViewRootImpl 的 invalidateChildInParent,然后触发peformTraversals,会导致当前 view 被重绘,由于 mLayoutRequested 为 false,不会导致 onMeasure 和 onLayout 被调用,而 OnDraw 会被调用
- 一个 view 的 invalidate 会导致本身 PFLAG_INVALIDATED 置 1,导致本身以及父族 viewgroup 的 PFLAG_DRAWING_CACHE_VALID 置 0
- requestLayout 会直接递归调用父窗口的 requestLayout,直到 ViewRootImpl,然后触发 peformTraversals,由于 mLayoutRequested 为 true,会导致 onMeasure 和onLayout 被调用。不一定会触发 OnDraw
- requestLayout 触发 onDraw 可能是因为在在 layout 过程中发现 l, t, r, b 和以前不一样,那就会触发一次 invalidate,所以触发了onDraw,也可能是因为别的原因导致 mDirty 非空(比如在跑动画)
- requestLayout 会导致自己以及父族 view 的 PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED 标志被设置。
- 一般来说,只要刷新的时候就调用 invalidate,需要重新 measure 就调用 requestLayout,后面再跟个 invalidate(为了保证重绘)