模型

image.png
参考 https://www.cnblogs.com/huansky/p/11911549.html

🎨 UI 层绘制 - 图2

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 中

carbon (1).png

ViewRootImpl

整个视图树最顶层的虚拟的爹
**

ViewRootImpl 与 DecorView 建立关系的过程

**
handleResumeActivity -> wm.addView(decor) -> WindowManagerImpl#addView -> WindowGlobal#addView -> 初始化 ViewRootImpl -> setView(decorView)

ViewRootImpl 控制 DecorView 递归绘制的过程

requestLayout() -> scheduleTraversals() -> doTraversal() -> performTraversals()

🎨 UI 层绘制 - 图4

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,则视图树的结构是这样的(默认主题):
image.png

继承 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)来实现自定义逻辑。

测量

image.png

目的 确定 View 的尺寸
MeasureSpec 32 位 Int 类型,高两位表示的是 SpecMode(测量模式),低 30 位表示的是 SpecSize(测量的具体大小) UNSPECIFIED
AT_MOST
EXACTY
LayoutParam 子 View 内部保存了其自身在父 View 中的布局参数
View# public final measure (widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension 调用该方法后便认为完成了自身的测量 protected final 子类只能调用不能重写

布局

image.png

目的 确定 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)_

_

    1. 调用子 View measure 方法,让其自我测量,计算子 View 的尺寸
    1. 根据子 View 的给出的尺寸,得出子 View 的位置并保存子 View 的位置和尺寸
    1. 根据子 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(为了保证重绘)