android view绘制郭林,Android View的绘制流程分析

1 Android UI 框架的核心类

在梳理View的绘制流程之前,我们先来熟悉一下AndroidUI框架中几个重要类的概念:

Window: 外观和行为策略的抽象基类,它的实例应该作为顶级视图被添加到 Window Manager 中。它提供标准的UI策略,例如 background,标题区域,默认关键处理等。

PhoneWindow: 是 Window 的唯一实现类,在 Activity 的 attach() 方法中构造一个 PhoneWindow 实例。

DecorView: 在 PhoneWindow 中创建的顶级视图,是 View Tree 的实际根节点,由此可见,View 是依附在 Window 之上的。

ViewRootImpl: 视图层次结构的顶部,在 View(DecorView) 和 WindowManager 之间实现所需的协议。 由 WindowMangerGlobal 的 addView() 方法创建。

WindowManagerImpl: 实现了 WindowManager 接口,是整个应用程序中所有 Window 的管理者。在 Activity 的 attach() 方法中由 PhoneWindow 调用 setWindowManager() 方法创建。

WindowManagerGlobal: 在 WindowManagerImpl 中创建的单例对象,在整个 Android 进程中是一个全局变量,它包含了能和 WMS 进行双向 Binder 通信的通道。一个应用进程中所有的 Activity 都是通过这个进程内唯一的 WindowManagerGlobal 对象和 WMS 进行通信。

WindowManagerService: 远程窗口管理类,从它的角度,并不关心应用中的各种View,它管理和调度的对象是Window。

327da9dd424d

WindowManager 类关系图

总结:

在 Activity 的 attach() 方法中,会创建 PhoneWindow 和 WindowMangerImpl 实例。而 WindowMangerImpl 持有 WindowManagerGlobal 的引用。

在 Activity 的 onCreate() 回调方法中,setContentView() 方法会调用 PhoneWindow#setContentView() 方法,如果 DecorView 对象还未创建,会调用 installDecor() 方法来创建 Activity 中的 DecorView。并将 inflate 出的 View 添加到 DecorView的 ContentView 中,而 ContentView 继承自 FrameLayout。

在 Activity 的 makeVisible() 方法中,调用了 WindowManager (WindowManagerGlobal) 的 addView() 方法,参数是 DecorView 对象,在addView() 方法中最重要的是创建了ViewRootImpl 对象,并通过 setView() 方法把 ViewRootImpl 对象和 DecorView 对象关联在一起。

WindowManagerService 通过与 WindowManagerGlobal 的双向 Binder 通信完成对 Window (PhoneWindow) 的管理。

WindowManagerGlobal 通过 ViewRootImpl 完成对 Window (PhoneWindow) 和 View (DecorView) 的管理。

2 View 的绘制流程分析

下面开始对View的绘制流程进行梳理:

首先,View Tree 发生重绘基本都会跟 View # requestLayout() 或 View # invalidate() 方法的调用相关。

那么我们首先来看一下 View 中的 requestLayout() 方法的源码

327da9dd424d

View # requestLayout()

可以看到,16行,在 mPrivateFlags 中添加了 PFLAG_FORCE_LAYOUT 标记,该标记将在之后的 measure 流程中标记哪些View 需要重新进行尺寸的测量。第20行,调用了父View 中的 requestLayout() 方法,该方法会一直向上传递,直到父View 是 ViewRootImpl,最终调用 ViewRootImpl # requestLayout() 方法,开始一次 View Tree 遍历。

那么我们来看一下 ViewRootImpl 中 requestLayout() 中的源码

327da9dd424d

ViewRootImpl # requestLayout()

在 RootViewImpl 类中,requestLayout() 和 invalidate() 方法都会调用 scheduleTraversals(),这是一个异步方法,最终会调用到 performTraversals() 方法,它是 View 绘制流程的核心方法,分别调用 performMeasure()、performLayout()、performDraw() 方法来进行 View 的尺寸测量 (measure),位置测量 (layout),绘制 (draw) 三大流程。

327da9dd424d

RootViewImpl # performTraversals()

2.1 measure 测量 View 的大小

在 ViewRootImpl # performTraversals() 中我们看到调用了 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 方法,参数 childWidthMeasureSpec 和 childHeightMeasureSpec 是 MeasureSpec 类型,分别表示了该 ViewRootImpl 关联的 顶层 View 对象的尺寸。注意,该顶层 View 在Activity 中是 DecorView,但是若只是通过 WindowManger # addView添加的悬浮框则不是。(MeasureSpec 的概念不再赘述)

若顶层 View 是 DecorView 对象,DecorView 继承自 FrameLayout,而 ViewGroup 继承自 View 类,且 View 的 measure() 方法是 final 修饰的,并不能被子类重写,因此我们来一起看一下这个 View # measure() 方法。

327da9dd424d

View # measure

可以看到,第4行中,使用到了起初 View # requstLayout() 中进行标记的 PFLAG_FORCE_LAYOUT, 用以表明该 View 是否需要被重新测量(measure),在27行,调用了onMeasure 方法,由于 DecorView 是 FrameLayout 子类,因此它实际上调用的是 DecorView # onMeasure()方法。37行,mPrivateFlags 添加了 PFLAG_LAYOUT_REQUIRED 标记,表示该 View 需要被重新布局(layout)。下面我们来看一下 FrameLayout # onMeasure() 方法。

327da9dd424d

FrameLayout # onMeasure()

FrameLayout # onMeasure() 方法, 20行,调用 measureChildWithMargins() 对子 View 进行测量,该方法需要除去父容器的 padding 和 margin之后计算子 View 的大小,这个过程会递归测量所有的子View 直到达到叶子节点。maxWidth 和 maxHeight 记录了子类中的最大宽度和最大高度,并添加 padding ,并把测量大小赋值给该 FrameLayout。

概括一下整个流程:测量始于DecorView,通过不断的遍历子View的measure方法,根据ViewGroup的MeasureSpec及子View的LayoutParams来决定子View的MeasureSpec,进一步获取子View的测量宽高,然后逐层返回,不断保存ViewGroup的测量宽高。

2.2 layout 计算 View 的位置

对于DecorView来说,调用layout方法,就是对它自身进行布局,注意到传递的参数分别是0, 0, host.getMeasuredWidth, host.getMeasuredHeight,它们分别代表了一个View的上下左右四个位置,显然,DecorView的左上位置为0,然后宽高为它的测量宽高。

327da9dd424d

View # layout()

在13行,setOpticalFrame(l, t, r, b) 和 setFrame(l, t, r ,b) 都是对 View 的左上角和右下角进行赋值,而在15行,当 changed = true 表示View 的位置发生了变化,或者 PFLAG_LAYOUT_REQUIRED 标记满足时,会调用 View 的 onLayout() 方法,若该方法在 ViewGroup 中调用,用于确定子 View 的位置,即在该方法内部,子View会调用自身的 layout 方法来进一步完成自身的布局流程。由于不同的布局容器的onMeasure方法均有不同的实现,现在仍然以 FrameLayout # onLayout 方法进行讲解。

327da9dd424d

FrameLayout # onLayout() 和 layoutChildren()

327da9dd424d

FrameLayout # onLayout() 和 layoutChildren()

在 FrameLayout 的 onLayout() 方法中,不需要考虑其他常用布局如 LinearLayout,GridLayout 等有各子对象竞争布局空间的问题,FrameLayout 中的各子View 是“层叠”而成的,只需要考虑到 Gravity 属性来确定子View 相对于父布局的位置。

第5行,在 onLayout 方法内部直接调用了layoutChildren方法

先梳理一下以上逻辑:首先先获取父容器的 padding 值,然后遍历其每一个子 View,根据子View 的 layout_gravity 属性、子View的测量宽高、父容器的 padding 值、来确定子View的布局参数,然后调用 child.layout 方法,把布局流程从父容器传递到子元素。

那么,现在就分析完了ViewGroup的布局流程,那么我们接着分析子元素的布局流程。

子View的布局流程

子View的布局流程也很简单,如果子View 是一个 ViewGroup,那么就会重复以上步骤,如果是一个View,那么会直接调用 View # layout 方法,根据以上分析,在该方法内部会设置 view 的四个布局参数,接着调用onLayout方法,我们看看 View # onLayout方法:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

2.3 draw 绘制 View 的内容

一个 View 的 layout 确定之后,才能在此基础上执行 draw 流程。

由于 ViewGroup 没有重写 draw 方法,因此所有的 View 都是调用 View#draw方法,因此,我们直接看它的源码

327da9dd424d

View # draw()

可以看到,draw过程比较复杂,但是逻辑十分清晰,而官方注释也清楚地说明了每一步的做法。我们首先来看一开始的标记位dirtyOpaque,该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等。这样很容易理解,因为一个View既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,这里先小结这六个步骤分别是什么,然后再展开来讲。

绘制流程的六个步骤:

1、对View的背景进行绘制

2、保存当前的图层信息(可跳过)

3、绘制View的内容

4、对View的子View进行绘制(如果有子View)

5、绘制View的褪色的边缘,类似于阴影效果(可跳过)

6、绘制View的装饰(例如:滚动条)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值