一. 前言

1. 作用

绘制 View视图。

二. 单一View的Draw过程

1. 原理

① View绘制自身,包括背景和内容。

② 绘制装饰,包括滚动指示器,滚动条和前景。

2. 过程

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
/**
* 源码分析:draw()
* 作用:根据给定的 Canvas 自动渲染 View(包括其所有子 View)。
* 绘制过程:
* 1. 绘制view背景
* 2. 绘制view内容
* 3. 绘制子View
* 4. 绘制装饰(渐变框,滑动条等等)
* 注:
* a. 在调用该方法之前必须要完成 layout 过程
* b. 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写此方法)
* c. 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制
* d. 若自定义的视图确实要复写该方法,那么需先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制
*/
public void draw(Canvas canvas) {

...// 仅贴出关键代码

int saveCount;

// 步骤1: 绘制本身View背景
if (!dirtyOpaque) {
drawBackground(canvas);
}

// 若有必要,则保存图层(还有一个复原图层)
// 优化技巧:当不需绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
// 因此在绘制时,节省 layer 可以提高绘制效率
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {

// 步骤2:绘制本身View内容
if (!dirtyOpaque) {
onDraw(canvas);
}
// View 中:默认为空实现,需复写
// ViewGroup中:需复写

// 步骤3:绘制子View
// 由于单一View无子View,故View 中:默认为空实现
// ViewGroup中:系统已经复写好对其子视图进行绘制我们不需要复写
dispatchDraw(canvas);

// 步骤4:绘制装饰,如滑动条、前景色等等
onDrawForeground(canvas);

return;
}
...
}

/**
* 步骤1:drawBackground(canvas)
* 作用:绘制View本身的背景
*/
private void drawBackground(Canvas canvas) {
// 获取背景 drawable
final Drawable background = mBackground;
if (background == null) {
return;
}
// 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界
setBackgroundBounds();

.....

// 获取 mScrollX 和 mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
// 若 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移
canvas.translate(scrollX, scrollY);


// 调用 Drawable 的 draw 方法绘制背景
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}

/**
* 步骤2:onDraw(canvas)
* 作用:绘制View本身的内容
* 注:
* a. 由于 View 的内容各不相同,所以该方法是一个空实现
* b. 在自定义绘制过程中,需由子类去实现复写该方法,从而绘制自身的内容
* c. 谨记:自定义View中 必须 且 只需复写onDraw()
*/
protected void onDraw(Canvas canvas) {

... // 复写从而实现绘制逻辑

}

/**
* 步骤3: dispatchDraw(canvas)
* 作用:绘制子View
* 注:由于单一View中无子View,故为空实现
*/
protected void dispatchDraw(Canvas canvas) {

... // 空实现

}

/**
* 步骤4: onDrawScrollBars(canvas)
* 作用:绘制装饰,如 滚动指示器、滚动条、和前景等
*/
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);

final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}

final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}

foreground.draw(canvas);
}
}

总结:

draw方法里面:依次调用drawBackground -> onDraw -> dispatchDraw(空实现) -> onDrawForeground

三. 多个View,ViewGroup的Draw过程

1. 原理

① ViewGroup绘制自身,包括背景、内容。

② ViewGroup遍历子View & 绘制其所有子View。

③ 绘制装饰,包括滚动指示器,滚动条和前景。

2. 过程

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
/**
* 源码分析:draw()
* 作用:根据给定的 Canvas 自动渲染 View(包括其所有子 View)。
* 绘制过程:
* 1. 绘制view背景
* 2. 绘制view内容
* 3. 绘制子View
* 4. 绘制装饰(渐变框,滑动条等等)
* 注:
* a. 在调用该方法之前必须要完成 layout 过程
* b. 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写此方法)
* c. 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制
* d. 若自定义的视图确实要复写该方法,那么需先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制
*/
public void draw(Canvas canvas) {

...// 仅贴出关键代码

int saveCount;

// 步骤1: 绘制本身View背景
if (!dirtyOpaque) {
drawBackground(canvas);
}

// 若有必要,则保存图层(还有一个复原图层)
// 优化技巧:当不需绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
// 因此在绘制时,节省 layer 可以提高绘制效率
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {

// 步骤2:绘制本身View内容
if (!dirtyOpaque) {
onDraw(canvas);
}
// View 中:默认为空实现,需复写
// ViewGroup中:需复写

// 步骤3:绘制子View
// 由于单一View无子View,故View 中:默认为空实现
// ViewGroup中:系统已经复写好对其子视图进行绘制我们不需要复写
dispatchDraw(canvas);

// 步骤4:绘制装饰,如滑动条、前景色等等
onDrawForeground(canvas);

return;
}
...
}

由于 步骤2:drawBackground、步骤3:onDraw、步骤5:onDrawForeground,与单一View的draw过程类似,此处不作过多描述。

进入与单一View不同的 dispatchDraw过程:

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
/**
* 源码分析:dispatchDraw()
* 作用:遍历子View & 绘制子View
* 注:
* a. ViewGroup中:由于系统为我们实现了该方法,故不需重写该方法
* b. View中默认为空实现(因为没有子View可以去绘制)
*/
protected void dispatchDraw(Canvas canvas) {
......

// 1. 遍历子View
final int childrenCount = mChildrenCount;
......

for (int i = 0; i < childrenCount; i++) {
......
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 2. 绘制子View视图 ->>分析1
more |= drawChild(canvas, transientChild, drawingTime);
}
....
}
}

/**
* 分析1:drawChild()
* 作用:绘制子View
*/
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// 最终还是调用了子 View 的 draw ()进行子View的绘制
return child.draw(canvas, this, drawingTime);
}

总结:

draw方法里面:依次调用drawBackground -> onDraw -> dispatchDraw(系统已经写好) -> onDrawForeground

参考文章

(4)自定义View Draw过程- 最易懂的自定义View原理系列