注:本文分析的源码基于 Android API 25
View绘制的起点
WindowManagerGlobal
addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow)
在 WindowManagerGlobal
的 addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow)
方法中,创建了 ViewRootImpl
对象,将 ViewRootImpl
和 DecorView
相关联:
1 | root = new ViewRootImpl(view.getContext(), display); |
ViewRootImpl
是用来连接 WindowManager 和 DecorView 的桥梁。通俗地来讲,Window 和 View 就是通过 ViewRootImpl
来建立联系的。
而 DecorView 是顶级的 View ,从它开始向下传递 measure 、 layout 和 draw 三个流程。
创建好了 root
之后,调用了 ViewRootImpl
的 setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
方法。
将 DecorView 和 ViewRootImpl 相关联。
ViewRootImpl
setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
1 | public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { |
在 setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
方法中,主要做的事情有:
- 保存 DecorView
- 第一次调用
requestLayout()
,发起整个 View 的绘制流程 - 将 View 添加到 Window 上去
而在这,我们重点关注 requestLayout()
方法,因为恰恰这句代码引发了整个 View 的绘制。
requestLayout()
1 | @Override |
在 requestLayout()
中先检查了线程,若 OK 后调用 scheduleTraversals()
。
scheduleTraversals()
1 | void scheduleTraversals() { |
在 scheduleTraversals()
中,其实是这样的:
scheduleTraversals() -> 调用 mTraversalRunnable -> doTraversal() -> performTraversals()
所以最后还是要看 performTraversals()
。
performTraversals()
1 | private void performTraversals() { |
performTraversals()
方法的代码很长很长,但是我们关注点就可以放在三大流程上。其他的代码因为自己能力欠缺,并不能一一说出这些代码的作用。所以我们接下来就把重点放在:
- getRootMeasureSpec
- performMeasure
- performLayout
- performDraw
三大流程
ViewRootImpl
measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight)
其实在 performTraversals()
中有一句代码
1 | // Ask host how big it wants to be |
在 measureHierarchy
方法中已经调用了 performMeasure
来进行测量。不过作用不同,只是为了确定 window 的大小而做的测量辅助。所以可以说,并不算上在三大流程中。
在 measureHierarchy
中,确定了 DecorView 的 MeasureSpec
。其中 childWidthMeasureSpec
和 childHeightMeasureSpec
即为 DecorView 对应的 MeasureSpec
。
1 | // desiredWindowWidth 和 desiredWindowHeight 是屏幕的宽高 |
getRootMeasureSpec(int windowSize, int rootDimension)
那么就来看看 getRootMeasureSpec
咯。
1 | private static int getRootMeasureSpec(int windowSize, int rootDimension) { |
代码很简洁,也很易懂。
- 如果是 MATCH_PARENT ,那么对应的就是窗口大小;
- 如果是 WRAP_CONTENT ,那么不能超过窗口大小;
- 固定大小,那么就是大小就是传入的 lp.width/lp.height 了。
ViewGroup
getChildMeasureSpec(int spec, int padding, int childDimension)
顺便,我们把平时自定义 ViewGroup 计算子 View 测量规格的 getChildMeasureSpec
方法也一起来看看:
1 | public static int getChildMeasureSpec(int spec, int padding, int childDimension) { |
上面的 switch/case 代码比较简单,而且容易理解。我们可以整理为一张表格(该表格来自于《Android开发艺术探索》):
在这里,我们小结一下。对于 DecorView 来说,其 MeasureSpec
是由窗口的大小和自身的 LayoutParams
来共同决定的;而对于普通的 View 来说,其 MeasureSpec
是由父容器的 MeasureSpec
和自身的 LayoutParams
共同决定的。
measure过程
ViewRootImpl
performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)
分析 measure 过程,我们的起点就是在 ViewRootImpl
的 performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)
方法中:
1 | private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { |
在 performMeasure
中调用了 measure
方法。说到底,DecorView 只是一个所以我们又要进入 View
类中去看下。
View
measure(int widthMeasureSpec, int heightMeasureSpec)
1 | public final void measure(int widthMeasureSpec, int heightMeasureSpec) { |
View
的 measure
方法内部是调用了 onMeasure
。所以我们还要接着跟进到 onMeasure
中才行。另外, measure
方法是用 final 修饰的,所以子类是无法进行重写的。
FrameLayout
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
这里小提一下,我们都知道 DecorView 其实是一个 FrameLayout
,所以 onMeasure
应该在 FrameLayout
中去看:
1 | @Override |
如果上面 FrameLayout
的 onMeasure
流程没看懂的话也没关系。其实总的来说重要的就只有遍历 child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
这个方法,这是将父容器的 measure 过程传递到子 View 中。
ViewGroup
measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
可能有些人也有疑问,在上面 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
后也没看到有 child.measure
的方法啊,这是因为在 measureChildWithMargins
中内部调用了 child.measure
:
1 | protected void measureChildWithMargins(View child, |
这下明白了吧?父容器就是遍历调用了 child.measure
这个方法将 measure 过程传递给每一个子 View 的。虽然不同的父容器 onMeasure
方法都不一样,但是相同的是,他们都会遍历调用 child.measure
。
View
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
上面我们也讲过,measure
方法内部其实是调用了 onMeasure
,所以子 View 被父容器调用了 measure
后,也会调用属于自己的 onMeasure
方法。那么我们就直接看向 View
的 onMeasure
方法:
1 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
onMeasure
方法只有一句代码,所以重点就是 getDefaultSize(int size, int measureSpec)
咯。
getSuggestedMinimumWidth()
内部逻辑:
- 若没有设置背景,就是
android:minWidth
的值; - 若有设置背景,就是 max(android:minWidth, 背景 Drawable 的原始宽度)
getSuggestedMinimumHeight()
也是同理。
getDefaultSize(int size, int measureSpec)
1 | public static int getDefaultSize(int size, int measureSpec) { |
从上面我们可以看到:
- 若是 UNSPECIFIED ,则直接返回的就是
getSuggestedMinimumWidth/getSuggestedMinimumHeight
的值; - 若是 AT_MOST/EXACTLY ,直接用的就是 specSize 。
而根据我们之前总结出来的表可知,只要 view 不指定固定大小,那么无论是 AT_MOST 还是 EXACTLY ,都是按照 parentSize 来的。
这也是为什么我们在自定义 View 时,如果不重写 onMeasure(int widthMeasureSpec, int heightMeasureSpec)
,wrap_content 和 match_parent 效果一样的原因。
小结
我们把 measure 过程的代码流程理一下:
ViewRootImpl.performTraversals -> ViewRootImpl.performMeasure -> DecorView.measure -> DecorView.onMeasure -> DecorView.measureChildWithMargins -> ViewGroup.measure -> ViewGroup.onMeasure -> ViewGroup.measureChildWithMargins -> … -> View.measure -> View.onMeasure
注:DecorView 其实就是 FrameLayout
layout过程
ViewRootImpl
performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)
在上面分析过,layout 过程是从 ViewRootImpl
中的 performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)
开始的。
1 | private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, |
基本可知,performLayout
是通过调用 DecorView 的 layout
方法来向下传递布局的。所以我们应该继续追踪 FrameLayout
的 layout
方法,其实就是 ViewGroup
的 layout
方法。
ViewGroup
layout(int l, int t, int r, int b)
FrameLayout
的 layout
是父类 ViewGroup
实现的,添加了 final 修饰符,无法被重写:
1 | @Override |
在 ViewGroup
的 layout
方法中又调用了父类的方法 super.layout(l, t, r, b)
。所以我们又要到 View
类中去看。
View
layout(int l, int t, int r, int b)
1 | @SuppressWarnings({"unchecked"}) |
上面的代码中做了这几件事:
- 设置当前布局中的四个顶点;
- 调用
setFrame
来设置新的顶点位置; - 调用
onLayout
方法; - 回调布局位置改变监听器;
setOpticalFrame(int left, int top, int right, int bottom)
我们先来看 setOpticalFrame
方法:
1 | private boolean setOpticalFrame(int left, int top, int right, int bottom) { |
其实在 setOpticalFrame
的内部也是调用 setFrame
方法的。
setFrame(int left, int top, int right, int bottom)
1 | protected boolean setFrame(int left, int top, int right, int bottom) { |
先回根据新旧的宽高进行比较,来确定是不是大小被改变了。如果是,会回调 sizeChange(newWidth, newHeight, oldWidth, oldHeight)
方法,这个方法是不是很眼熟呢?
之后还会把这消息通知给 AccessibilityService
无障碍服务。
最后返回布局是否改变的 boolean 值。
FrameLayout
onLayout(boolean changed, int left, int top, int right, int bottom)
接着,根据布局改变值 changed
会调用 onLayout
方法。
onLayout
方法在 View/ViewGroup 都是空的,是需要子类来实现的。所以我们还是要看 FrameLayout
中的 onLayout
:
1 | @Override |
在 onLayout
中调用了 layoutChildren
方法。
layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity)
1 | void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { |
简单来说,在 layoutChildren
中,遍历所有可见的子 View ,然后得到它们的宽高。
再根据不同的 gravity 来计算 childLeft 和 childTop ,最后调用 child.layout 来向子 View 传递下去。
小结
我们把 layout 过程的代码流程理一下:
ViewRootImpl.performTraversals -> ViewRootImpl.performLayout -> DecorView(ViewGroup).layout -> View.layout -> DecorView(FrameLayout).onLayout -> DecorView(FrameLayout).layoutChildren -> ViewGroup.layout -> View.layout -> ViewGroup.onLayout -> … -> View.layout -> View.onLayout
注:
- ViewGroup.onLayout 是抽象方法,根据不同的 ViewGroup 都有不同的实现方式。但是相同的是,都会遍历调用 child.layout 方法;
- View.onLayout 是空方法;
draw过程
最后一个,draw 过程。 draw 过程应该来说是比较简单的。
ViewRootImpl
performDraw()
首先起点是 performDraw()
方法。
1 | private void performDraw() { |
如果是第一次绘制视图,那么显然应该绘制所有的视图,fullRedrawNeeded
参数就为 true ;反之如果由于某些原因,导致了视图重绘,那么就没有必要绘制所有视图,即为 false 。
draw(boolean fullRedrawNeeded)
performDraw()
内部又调用了私有方法 draw(boolean fullRedrawNeeded)
:
1 | private void draw(boolean fullRedrawNeeded) { |
在确定了绘制的区域 dirty
之后,调用了 drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty)
。
drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty)
1 | private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, |
View
draw(Canvas canvas)
之后调用了 View
的 draw(Canvas canvas)
:
1 | public void draw(Canvas canvas) { |
draw 过程大概有下面几步:
- 绘制背景:
background.draw(canvas)
; - 保存当前的图层信息(一般来说跳过);
- 绘制自己:
onDraw(canvas)
; - 绘制children:
dispatchDraw(canvas)
; - 绘制边缘效果,恢复图层(一般来说跳过);
- 绘制前景装饰:
onDrawForeground(canvas)
。
在这里,我们继续看一下 dispatchDraw(Canvas canvas)
方法,这个方法是向子 View 分发绘制流程的。
因为 View 没有子 View ,所以 dispatchDraw(Canvas canvas)
方法是空的,所以我们要到 ViewGroup 中去看看。
ViewGroup
dispatchDraw(Canvas canvas)
1 | @Override |
在 dispatchDraw(Canvas canvas)
中,遍历子 View ,然后调用 drawChild(Canvas canvas, View child, long drawingTime)
方法来执行子 View 的绘制流程。
drawChild(Canvas canvas, View child, long drawingTime)
1 | protected boolean drawChild(Canvas canvas, View child, long drawingTime) { |
发现在 drawChild(Canvas canvas, View child, long drawingTime)
中还是调用了 draw(Canvas canvas, ViewGroup parent, long drawingTime)
方法。但是这个 draw(Canvas canvas, ViewGroup parent, long drawingTime)
和上面的 draw(Canvas canvas)
参数不同,所以不是同一个方法。
View
draw(Canvas canvas, ViewGroup parent, long drawingTime)
1 | boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { |
在 draw(Canvas canvas, ViewGroup parent, long drawingTime)
中,若没有缓存的话:
- 若
willNotDraw
设置为 false 的话,那么调用draw(canvas)
; - 否则直接调用
dispatchDraw(canvas)
分发给子 View ,一般适用于 ViewGroup ;
willNotDraw
代表一个 View 不需要绘制任何内容的话,那么系统会跳过,进行性能上的优化。
到这里,就调用了子 View 的 draw(Canvas canvas)
方法,从而实现了绘制过程的向下传递。
小结
我们把 draw 过程的代码流程理一下:
ViewRootImpl.performTraversals -> ViewRootImpl.performDraw -> ViewRootImpl.draw(boolean fullRedrawNeeded) -> ViewRootImpl.drawSoftware -> DecorView(View).draw(Canvas canvas) -> DecorView(ViewGroup).dispatchDraw -> DecorView(ViewGroup).drawChild -> ViewGroup(View).draw(Canvas canvas, ViewGroup parent, long drawingTime) -> ViewGroup.dispatchDraw -> ViewGroup.drawChild -> ViewGroup.draw(Canvas canvas, ViewGroup parent, long drawingTime) -> … -> View.draw(Canvas canvas) -> View.onDraw -> View.dispatchDraw
注:
- 其中
View.dispatchDraw
为空实现; - DecorView 在
draw(Canvas canvas)
的方法内不会调用onDraw
方法; - ViewGroup 不会调用
draw(Canvas canvas)
方法;
最后
总体来说,三个流程中主要还是 measure 过程较复杂。其他的两个流程整体上来说还是比较清晰简单的。
可以说 View 工作的三大流程是每一位 Android 开发者都必须掌握的。之前虽然也了解,但是没有写成博客好好捋一下,现在终于完成了,篇幅真的太长了。 ^_^
另外,除了需要了解这三大流程外,还需要知道 requestLayout
和 invalidate
等方法的原理。这些东西等有空了我理一理再写出来给大家吧。
今天就这样了,如果有不懂的地方可以在下面留言。