android View 详解(二)

How android draw Views

首先,看一下开发文档how android draw views这个文档讲的比较空泛,先读一下,有个简单的了解.简单翻译一下。

When an Activity receives focus, it will be requested to draw its layout. The Android framework will handle the procedure for drawing, but the Activity must provide the root node of its layout hierarchy.

Drawing begins with the root node of the layout. It is requested to measure and draw the layout tree. Drawing is handled by walking the tree and rendering each View that intersects the invalid region. In turn, each ViewGroup is responsible for requesting each of its children to be drawn (with the draw() method) and each View is responsible for drawing itself. Because the tree is traversed in-order, this means that parents will be drawn before (i.e., behind) their children, with siblings drawn in the order they appear in the tree.

绘制从布局的root node开始,需要测量和绘制layout tree。绘制是通过遍历树,并且呈现出每个View相交的无效区域。反过来,每个ViewGroup负责它的每个孩子的绘制,每个View负责绘制它本身。因为树是先序遍历的,所以parents必须在它们的children之前被绘制,与它们在树中出现的相同顺序绘制兄弟node。

Note:

The framework will not draw View objects that are not in the invalid region, and also will take care of drawing the View background for you.

You can force a View to draw, by calling invalidate().

Drawing the layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in measure(int, int) and is a top-down traversal of the View tree. Each View pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every View has stored its measurements. The second pass happens in layout(int, int, int, int) and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.

有两个passa measure pass and a layout pass(自顶向下遍历树)。Measure结束时每个View存储了自己的尺寸;之后layout pass开始执行:每个parents用来自measure pass的数据来布局自己的children在自身中的位置。

When a View object’s measure() method returns, its getMeasuredWidth() and getMeasuredHeight() values must be set, along with those for all of that View object’s descendants. A View object’s measured width and measured height values must respect the constraints imposed by the View object’s parents. This guarantees that at the end of the measure pass, all parents accept all of their children’s measurements. A parent View may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children’s unconstrained sizes is too big or too small (that is, if the children don’t agree among themselves as to how much space they each get, the parent will intervene and set the rules on the second pass).

当一个View对象的measure()方法执行结束时,这个View以及它的后代的getMeasuredWidth() and getMeasuredHeight() values must be set。并且一个View对象的测量得到的width和height必须遵循该View的parent的限制。这保证了在测量阶段结束时,所有的父母接受所有子女的尺寸。

Note:

To initiate a layout, call requestLayout(). This method is typically called by a View on itself when it believes that is can no longer fit within its current bounds.

View Measure过程

The measure pass uses two classes to communicate dimensions. The ViewGroup.LayoutParams class is used by View objects to tell their parents how they want to be measured and positioned. The base ViewGroup.LayoutParams class just describes how big the View wants to be for both width and height. For each dimension, it can specify one of:

measure pass通过两个classes来表达尺寸。

ViewGroup.LayoutParams

ViewGroup.LayoutParams负责描述被绘制View的width和height,它的值时下方中的一种:

  • an exact number

  • MATCH_PARENT, which means the View wants to be as big as its parent (minus padding)

  • WRAP_CONTENT, which means that the View wants to be just big enough to enclose its content (plus padding).

    针对ViewGroup的不同子类实现了ViewGroup.LayoutParams的子类。

MeasureSpec

MeasureSpec对象用于推动自顶向下绘制View的需求。一个MeasureSpec可以在三种模式中的一种:

  • UNSPECIFIED: This is used by a parent to determine the desired dimension of a child View. For example, a LinearLayout may call measure() on its child with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how tall the child View wants to be given a width of 240 pixels.

  • EXACTLY: This is used by the parent to impose an exact size on the child. The child must use this size, and guarantee that all of its descendants will fit within this size.

  • AT MOST: This is used by the parent to impose a maximum size on the child. The child must guarantee that it and all of its descendants will fit within this size.

好了,如果你的目的是简单的了解View的绘制过程,那么你的目的已经达到了。

View 绘制流程深入

参考,《android 内核剖析》 柯元旦向大牛致敬!!!

准备知识

首先,需要明确一些androidUI相关的一些概念

  • Activity:Activity包含一个Window,该Window在Activity的attach方法中通过调用PolicyManager.makeNewWindo创建;
  • View:最基本的UI组件,表示屏幕上的一个矩形区域;
  • DecorView:是Window中View的RootView,设置窗口属性;
  • Window:表示顶层窗口,管理界面的显示和事件的响应;每个Activity 均会创建一个 PhoneWindow对象,是Activity和整个View系统交互的接口
  • WindowManager:一个interface,继承自ViewManager。所在应用进程的窗口管理器;有一个implementation WindowManagerImpl;主要用来管理窗口的一些状态、属性、view增加、删除、更新、窗口顺序、消息收集和处理等。
  • ViewRoot:通过IWindowSession接口与全局窗口管理器进行交互:界面控制和消息响应;
  • ActivityThread:应用程序的主线程,其中会创建关联当前Activity与Window;创建WIndowManager实现类实例,把当前DecoView加入到WindowManager;

    android UI架构图如下:

    View绘制流程的函数调用链如下:

    图片来源 https://plus.google.com/+ArpitMathur/posts/cT1EuBbxEgN

假设,我们有两个Activity,分别是ActivityA通过startActivity()来加载ActivityB让我们先来回顾一下,这个加载过程:

MeasureSpec类

因为在View加载过程中需要它来传递一些信息,所以先来看一下这个类。MeasureSpec是View的一个静态内部类。主要职能是将父View的layout requirement 传递给子View Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:

  • UNSPECIFIED

    The parent has not imposed any constraint on the child. It can be whatever size it wants.

  • EXACTLY
    The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.

  • AT_MOST

    The child can be as large as it wants up to the specified size.

以及,接下来要用到的三个method

  • getMode(int measureSpec)

    Extracts the mode from the supplied measure specification.获取mode

  • getSize(int measureSpec)

    Extracts the size from the supplied measure specification.获取size(width/height)

  • makeMeasureSpec(int size, int mode)

    Creates a measure specification based on the supplied size and mode.(封装一个MeasureSpec对象)

接下来,我们来从源码的角度分析Measure过程

View绘制整体流程梳理

首先需要明确一点,android UI框架绘制View是从ViewRootImpl.java类开始的。

Note:ViewRoot自2.3.7版本之后就被ViewRootImpl类取代了。

View树的绘制是从ViewRootImpl类的private void performTraversals()方法开始的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//开始View绘制流程
private void performTraversals(){
// cache mView since it is used so much below...
final View host = mView;
...

//这两个值是由MeasureSpec.makeMeasureSpec()构建的。
int desiredWindowWidth;
int desiredWindowHeight;

...

//
if (!mStopped) {
...
//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

...

performLayout();

...

mView.draw(canvas);
//performTraversals()方法负责了View绘制的三个流程,其他细节代码省略
}

performMeasure源码

1
2
3
4
5
6
7
8
9
10
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//因为View的measure方法是public final,所以此处调用View的measure()方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
~

performLayout源码

1
2
3
4
5
6
7
8
9
10
11
12
13
private void performLayout() {
mLayoutRequested = false;
mScrollMayChange = true;

...

try {
此处调用View的layout()方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

getRootMeasureSpec()源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {

case ViewGroup.LayoutParams.MATCH_PARENT:
//RootView强制走MATCH_PARENT case,这就是为何RootView总是全屏
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

这里并不需要多说什么。看一下流程图。对于这部分的理解可以参照LayoutInflater加载XML文件的机制,二者的整体思路是一致的。

Measure过程

调用View.measure()方法进行预处理

因为measure()方法是final修饰,所以View的所有子类都不能重载该方法。所以View子类只能通过重载onMeasure来实现自己的测量逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判断是否为强制布局,即带有“FORCE_LAYOUT”标记 以及 widthMeasureSpec或heightMeasureSpec发生了改变
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {

// first clears the measured dimension flag
//清除MEASURED_DIMENSION_SET标记 ,该标记会在onMeasure()方法后被设置
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

resolveRtlPropertiesIfNeeded();

// measure ourselves, this should set the measured dimension flag back
// 1、 测量该View本身的大小 ; 2 、 设置MEASURED_DIMENSION_SET标记,否则接写来会报异常
onMeasure(widthMeasureSpec, heightMeasureSpec);

// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}

//下一步是layout了,添加LAYOUT_REQUIRED标记
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}

//参数widthMeasureSpec和heightMeasureSpec 由父View构建,表示父View给子View的测量要求
mOldWidthMeasureSpec = widthMeasureSpec;//保存值,下次递归时会作为旧值进行比较
mOldHeightMeasureSpec = heightMeasureSpec;//保存值
}

其中onMeasure(widthMeasureSpec, heightMeasureSpec)的参数widthMeasureSpec和heightMeasureSpec 由父View构建,表示父View给子View的测量要求。

回调View.onMeasure()方法设置View的Width和Height

1
2
3
4
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

onMeasure()方法只调用了setMeasuredDimension方法,setMeasuredDimension方法的作用是对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值。measure的主要目的就是对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值,所以一旦这两个变量被赋值意味着该View的测量工作结束。可以看到setMeasuredDimension方法的参数都是通过getDefaultSize()方法传入的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
//通过MeasureSpec解析获取mode与size
//@param size参数一般表示设置了android:minHeight属性或者该View背景图片的大小值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

//根据不同的mode值,取得宽和高的实际值。
switch (specMode) {
case MeasureSpec.UNSPECIFIED: //表示该View的大小父视图未定,设置为默认值
result = size;
break;
case MeasureSpec.AT_MOST://表示该View的大小由父视图指定了
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

//获得设置了android:minHeight属性或者该View背景图片的大小值, 作为该View的参考
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

}

//getDefaultSize参数的widthMeasureSpec和heightMeasureSpec都是由父View传递进来的
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

到了这里呢,非容器类的View的measure过程已经结束。
但是对于父View,也就是ViewGroup类型,都需要在重写onMeasure() 方法,遍历所有子View,设置每个子View的大小。基本思想如下:遍历所有子View,设置每个子View的大小。

整个Measure流程图如下:

ViewGroup的Measure过程梳理

ViewGroup中定义了measureChildren, measureChild, measureChildWithMargins方法来对子视图进行测量,measureChildren内部实质只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也作为子视图的大小。 而widthMeasureSpec、heightMeasureSpec也是在ViewGroup中设置的,它们向子View传达了父View对其子View的布局要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/

//遍历每个子View,然后调用measureChild()方法去实现每个子View大小
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
//依次遍历并measureViewGroup的每个子View
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {// 不处于 “GONE” 状态
//widthMeasureSpec 和 heightMeasureSpec 表示该父View的布局要求
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}

/*测量每个子View高宽时,清楚了该View本身的边距大小,即android:padding属性 或
android:paddingLeft等属性标记*/


protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();// 获取View的LayoutParams属性

/*设置子View的childWidthMeasureSpec属性,去除了该父View的边距值 mPaddingLeft +
mPaddingRight*/


final int childWidthMeasureSpec =
getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);

/*设置子View的childHeightMeasureSpec属性,去除了该父View的边距值 mPaddingTop +
mPaddingBottom*/


final int childHeightMeasureSpec =
getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);

//调用到View的 measure(int widthMeasureSpec, int heightMeasureSpec)方法
//因为该方法时是public final
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

对于这段代码的说明:

  • measureChildren()方法:遍历所有子View,调用measureChild()方法去设置该子View的属性值。
  • measureChild() 方法 : 获取特定子View的widthMeasureSpec、heightMeasureSpec,调
    用measure()方法设置子View的实际宽高值。
  • getChildMeasureSpec()就是获取子View的widthMeasureSpec、heightMeasureSpec值。根据
    父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View内部
    LayoutParams属性值,共同决定子View的measureSpec值的大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
   /**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view 表示该父View本身所占的widthMeasureSpec 或 heightMeasureSpec值
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* 表示该父View的边距大小,见于android:padding属性 或android:paddingLeft等属性标记
* @param childDimension How big the child wants to be in the current
* dimension 表示该子View内部LayoutParams属性的值,可以是wrap_content、match_parent、一个精确指(an exactly size), 例如:由android:width指定等。
* @return a MeasureSpec integer for the child
*/

//获取子View的widthMeasureSpec、heightMeasureSpec值
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取当前Parent View的Mode和Size
int specMode = MeasureSpec.getMode(spec);//获得父View的mode
int specSize = MeasureSpec.getSize(spec);//获得父View的实际值

//获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
int size = Math.max(0, specSize - padding);

//定义返回值存储变量
int resultSize = 0; //子View对应地 size 实际值 ,由下面的逻辑条件赋值
int resultMode = 0;//子View对应地 mode 值 , 由下面的逻辑条件赋值

//依据当前Parent的Mode进行switch分支逻辑
switch (specMode) {
// Parent has imposed an exact size on us
//默认Root View的Mode就是EXACTLY
//1、父View是EXACTLY的
case MeasureSpec.EXACTLY:
//如果child的layout_width属性在xml或者java中给予具体大于等于0的数值
//设置child的size为真实layout_width属性值,mode为EXACTLY
//1.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//如果child的layout_width属性在xml或者java中给予MATCH_PARENT
//设置child的size为size,mode为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
//1.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//WRAP_CONTENT
//设置child的size为size,mode为EXACTLY
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent has imposed a maximum size on us
//2、父View是AT_MOST的 !
case MeasureSpec.AT_MOST:
//2.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;//size为精确值
resultMode = MeasureSpec.EXACTLY;//mode为 EXACTLY
}
//2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;//size为父视图大小
resultMode = MeasureSpec.AT_MOST;//mode为AT_MOST
}
//2.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;//size为父视图大小
resultMode = MeasureSpec.AT_MOST;//mode为AT_MOST
}
break;

// Parent asked to see how big we want to be
//3、父View是UNSPECIFIED的 !
case MeasureSpec.UNSPECIFIED:
//3.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;//size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY
}
//3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;//size为0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED
}
//3.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0; //size为0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED;//mode为 UNSPECIFIED
}
break;
}
//根据上面逻辑条件获取的mode和size构建MeasureSpec对象。
//将mode与size通过MeasureSpec方法整合为32位整数返回
//返回给measureChildWithMargins方法
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

Measure原理总结

measure过程主要就是从顶层父View向子View递归调用view.measure方法(measure中又回调onMeasure方法)的过程。具体measure核心主要有如下几点:

  • MeasureSpec(View的内部类)测量规格为int型,其中specMode只有三种值:

    MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定;
    MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
    MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定;

  • View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。
  • 最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定
    的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
  • ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了
    父子View的尺寸计算。
  • 只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使
    用layout_margin参数。
  • View的布局大小由父View和子View共同决定。
  • 使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证
    这两个方法在onMeasure流程之后被调用才能返回有效值。

    Measure过程具体例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<RelativeLayout android:id="@+id/my_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/my_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"/>

</RelativeLayout>

对LinearLayout而言比较简单,由于 android:layout_width=”match_parent”,因此其width对
应地widthSpec mode值为MeasureSpec.EXACTLY , size由父视图大小指定 ; 由
于android:layout_height = “match_parent”,因此其height对应地heightSpec mode值
为MeasureSpec.EXACTLY,size由父视图大小指定 ;

对TextView而言 ,其父View为LinearLayout的widthSpec和heightSpec值皆
为MeasureSpec.EXACTLY类型,由于android:layout_width=”match_parent” , 因此其width
对应地widthSpec mode值为MeasureSpec.EXACTLY,size由父视图大小指定 ; 由
于android:layout_width=”wrap_content” , 因此其height对应地widthSpec mode值为
MeasureSpec.AT_MOST,size由父视图大小指定 。

接下俩,看一下LinearLayout类的measure过程

前面已经讲过,View的子类只能通过重载onMeasure方法来完成measure过程。

1
2
3
4
5
6
7
8
9
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//判断是垂直方向还是水平方向,这儿我们假设是VERTICAL垂直方向
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}

以measureVertical进行进一步分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

/**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
* @param heightMeasureSpec Vertical space requirements as imposed by the parent.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onMeasure(int, int)
*/

//垂直方向布局
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0; //该LinearLayout测量子View时的总高度
int maxWidth = 0; //保存子View中最大width值
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;//所有子View的权重和 , android:layout_weight

final int count = getVirtualChildCount();//子View的个数

//获取Mode类型
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

...

// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);

...
/*注意,我们将类型为 ViewGroup.LayoutParams的实例对象强制转换为了
LinearLayout.LayoutParams,即父对象转换为了子对象,能这样做的原因就
是LinearLayout的所有子View的LayoutParams类型都为LinearLayout.LayoutParams*/


LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();

totalWeight += lp.weight;
//满足该条件地View会在该LinearLayout有剩余高度时,才真正调用measure()
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight >
0) {
...
} else {
int oldHeight = Integer.MIN_VALUE;
/*如果View的hight值为0,并且设置了android:layout_weight属性,重新纠正其
height值为WRAP_CONTENT*/

if (lp.height == 0 && lp.weight > 0) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}

// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
//对每个子View调用measure()方法
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);

if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}

/**这三行代码做了如下两件事情:
* 1、获得该View的measuredHeight值,每个View都会根据他们地属性正确设置值 > 0 ;
* 2、更新mTotalLength值:取当前高度mTotalLength值与mTotalLength +
* childHeight的最大值.于是对于android:layout_height="wrap_height"属性地
* LinearLayout控件也就知道了它的确切高度值了。**/


final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight +
lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));

...
}
...

child.measure(childWidthMeasureSpec,
...

//得到maxWidth
maxWidth += mPaddingLeft + mPaddingRight;

// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec,
childState),
heightSizeAndState);

if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}

接下来看看measureChildWithMargins方法,因为measureChildBeforeLayout方法是封装了对它的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
   void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}

/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/

//基本流程同于measureChild()方法,但添加了对子View Margin的处理,即:android:margin属性或者android:marginLeft等属性的处理
//widthUsed参数 表示该父View已经使用的宽度
//heightUsed参数 表示该父View已经使用的高度

protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//获取子视图的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

//调整MeasureSpec
//通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);

//调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

可见,measureChildWithMargins()方法的基本流程同于measureChild()方法,但添加了对子View Margin的处理,即:android:margin属性或者android:marginLeft等属性的处理。

至此,关于Measure的内容完全结束。

Layout过程

回到最初的那行代码

1
2
//调用View.layout()方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

layout方法的四个参数分别代表子View在父View中的相对坐标。可见上,左都是0,而右(width),下(height)则是测量值。

那么,接下来让我们又回到View.java进行查看。而整个View.layout()的流程也是通过递归来完成的,整体和measure的流程图几乎一样。

接下来,就是查看源码了:

Layout过程梳理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  /**
* Assign a size and position to a view and all of its
* descendants
*
* <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().</p>
*
* <p>Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.</p>
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
//View自身调用来进行layout()过程
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量
//判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout
boolean changed = setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //需要重新layout
onLayout(changed, l, t, r, b);//该方法的body为空,所以需要具体的ViewGroup来实现,如LinearLayout
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}

同样,这里也是调用了onLayout()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

呵呵,看到了吧。onLayout方法是空的。接下来,再看看ViewGroup中的onLayout方法。

1
2
3
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b)
;

竟然是一个抽象方法,这就说明,Layout()方法只能由ViewGroup的子类来重载了。既然如此,那我么就看看LinearLayout里面的实现吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@Override//Layout 过程
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical();
} else {
layoutHorizontal();
}
}

//以layoutVertical()为例, layoutHorizontal()同理
/**
* Position the children during a layout pass if the orientation of this
* LinearLayout is set to {@link #VERTICAL}.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onLayout(boolean, int, int, int, int)
*/

void layoutVertical() {
final int paddingLeft = mPaddingLeft;

int childTop;
int childLeft;

// Where right end of child should go
//计算父窗口推荐的子View宽度
final int width = mRight - mLeft;
//计算父窗口推荐的子View右侧位置
int childRight = width - mPaddingRight;

// Space available for child
//child可使用空间大小
int childSpace = width - paddingLeft - mPaddingRight;

//通过ViewGroup的getChildCount方法获取ViewGroup的子View个数
final int count = getVirtualChildCount();

//获取Gravity属性设置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

//依据majorGravity计算childTop的位置值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + mBottom - mTop - mTotalLength;
break;

// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;
break;

case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}

//开始遍历每个子View,即为每个子View进行layout
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);//return 0;
} else if (child.getVisibility() != GONE) {
//子View测量所得width和height
//LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的
//意义就是为layout过程提供视图显示范围的参考值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();

//获取子View的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();

int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
//依据不同的absoluteGravity计算childLeft位置
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;

case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;

case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}

if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}

childTop += lp.topMargin;
//通过setChildFrame的封装调用View.layout()方法
//通过垂直排列计算调运child的layout设置child的位置
setChildFrame(child, childLeft, childTop +
getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin +
getNextLocationOffset(child);

i += getChildrenSkipCount(child, i);
}
}
}

可以看到layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排
子View在父View中显示的位置只能作为参考。同时也应记住,getMeasuredWidth()
、getMeasuredHeight()必须在onMeasure之后使用才有效;而getWidth()与getHeight()方法必须
在layout(int l, int t, int r, int b)执行之后才有效。

Layout原理总结

layout过程也是从View树自顶向下进行的。父View会根据measure所得到的数据将子View放在正确的
位置,并设置合适的宽高。

  • layout完成后得到的每个子View的坐标都是相对于它的parent的,如mTop,mRight,mBottom,
    mLeft。而measure所得到的measuredWidth和measuredHeight经过计算得出的绝对坐标。
  • 使用View的getWidth()和getHeight()方法必须在该View layout完成之后。
  • View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为
    abstract的,子类必须重载实现自己的位置逻辑。

Draw过程

draw()的调用位置和之前的两个方法的调用位置一样。draw过程的流程图如下

Draw过程梳理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/



public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) ==
PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/


// Step 1, draw the background, if needed
int saveCount;

if (!dirtyOpaque) {
//获取xml中通过android:background属性或者代码中setBackgroundColor()、
//setBackgroundResource()等方法进行赋值的背景Drawable
final Drawable background = mBackground;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;

if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}

if ((scrollX | scrollY) == 0) {
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}

// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);

// we're done...
return;
}

/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/


boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;

float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;

// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;

final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}

int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);

if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}

final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;

// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}

// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}

if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}

if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}

saveCount = canvas.getSaveCount();

int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}

if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}

if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}

if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}

// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);//均需要子类自己来实现

// Step 4, draw the children
dispatchDraw(canvas);//均需要子类自己来实现

// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;

if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, right, top + length, p);
}

if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, bottom - length, right, bottom, p);
}

if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, left + length, bottom, p);
}

if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(right - length, top, right, bottom, p);
}

canvas.restoreToCount(saveCount);

// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);//onDrawScrollBars为protected final
}

注释很清晰,整个Draw流程一共可以分为六个步骤,而且如果可以的话步骤2和5是可以跳过的。

第一步主要是完成背景的绘制,具体的可见注释。

第二步主要是对canvas进行保存,以实现渐变效果

第三步是绘制View,该onDraw()方法需要具体的子类来实现。

第四步主要是绘制View的子View,同样在View里面 dispatchDraw(canvas)方法也是空的。很明显,这个方法需要View的容器来实现。而通过查找源码发现RelativeLayout,LinearLayout等并没有实现该方法。最后查看ViewGroup.java发现ViewGroup实现了dispatchDraw(canvas)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
  @Override
protected void dispatchDraw(Canvas canvas) {
final int count = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;

if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;

final boolean buildCache = !isHardwareAccelerated();
//ViewGroup重载了View的dispatchDraw方法,并遍历自己的所有children,然后调用
//drawChild方法

...

if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
child.getAnimation() != null) {
//drawChild()方法封装了View.draw()方法的调用
more |= drawChild(canvas, child, drawingTime);
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}

// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
...
}


/**
* Draw one child of this View Group. This method is responsible for getting
* the canvas in the right state. This includes clipping, translating so
* that the child's scrolled origin is at 0, 0, and applying any animation
* transformations.
*
* @param canvas The canvas on which to draw the child
* @param child Who to draw
* @param drawingTime The time at which draw is occurring
* @return True if an invalidate() was issued
*/

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

可以看到,protected void dispatchDraw方法调用了drawChild方法,而drawChild方法回调了View.draw()方法,所以最终我们又回到了View.java。

1
2
3
4
5
6
7
8
9
10
11
 
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
* This draw() method is an implementation detail and is not intended to be overridden or
* to be called from anywhere else other than ViewGroup.drawChild().
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

...

}

通过注释可以看到,该方法是被ViewGroup.drawChild()回调,用来让child View完成对自身的绘制。
主要涉及到View的加载动画,cache,alpha等方面。
第五步负责绘制渐变效果,并存储相关的layers

第六步则是完成对滚动条的绘制,可参考ScrollView等。

Draw流程总结

一些关键的方法

  • drawChild(canvas, this, drawingTime)
    直接调用了 View 的child.draw(canvas, this,drawingTime)方法,文档中也说明了,除了
    被ViewGroup.drawChild()方法外,你不应该在其它任何地方去复写或调用该方法,它属于 ViewGroup。
    而View.draw(Canvas)方法是我们自定义控件中可以复写的方法,具体可以参考上述对view.draw(Canvas
    )的说明。从参数中可以看到,child.draw(canvas, this, drawingTime) 肯定是处理了和父视图相关
    的逻辑,但 View 的最终绘制,还是 View.draw(Canvas)方法。

  • invalidate()
    请求重绘 View 树,即 draw 过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些
    调用了invalidate()方法的 View。

  • requestLayout()
    当布局变化的时候,比如方向变化,尺寸的变化,会调用该方法,在自定义的视图中,如果某些情况下希望重新
    测量尺寸大小,应该手动去调用该方法,它会触发measure()和layout()过程,但不会进行 draw。

结语

至此,View绘制流程便结束了。后期会对View绘制中的一些重要方法进行学习。

今天看到一句很喜欢的话,作为结束吧。

要么学习;要么健身。别在最美的时光里变成胖子!