Android进阶 - View 工作原理探究

前言

探究分析了View绘制的总体流程:onMeasure、onLayout、onDraw三大方法。

知识准备

ViewRoot

ViewRoot对应ViewRootImpl类,是连接WindowManager与DecorView的纽带。View的三大流程都是通过ViewRoot完成的。ActivityThread中,Activity对象被回收时,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView对象建立关联。
代码示例:

1
2
3
4
//创建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(),display);
//添加关联
root.setView(view,wparams,panelparentView);

View绘制流程:从ViewRoot的performTraversals开始,经过measure、layout、draw三大流程后才将View绘制出来。
performTraversals方法(8W多行代码),会依次调用performMeasure、performLayout、performDraw方法(这三个方法分别完成顶级View - DecorView 的measure、layout、draw方法)

其中performMeasure方法会调用measure方法,在measure方法中又会调用onMeasure方法,onMeasure方法则会对所有子元素进行measure,从而达到measure流程从父容器传递到子元素中的目的。接着子元素会重复父容器的measure过程,如此反复完成整个View树的遍历。

最后,perfromLayout、performDraw的传递过程也是类似的,唯一不同,而performDraw的传递过程在draw方法中通过dispatchDraw来实现。

DecorView - 继承FrameLayout

DecorView作为顶级View,一般情况下会包含一个LinearLayout,该LinearLayout分为上下两部分,titlebar部分、android.R.id.content部分(所以,setContentView方法其实就是将布局添加到id为content的FrameLayout中)正如小标题,DecorView本质是FrameLayout,View层时间都必先经过DecorView然后在传递给其中的View。

MeasureSpec - 很大程度决定View的尺寸规格

MeasureSpec代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(规格大小)
小白科普:Java中int为4个字节,Android使用第一个高位字节存储Mode,剩下三个字节存储Size

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
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//下面是三种测量模式
/**
* 父控件不对子控件施加任何约束,一般用于系统内部
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* 父控件已为子控件指定精确大小,对应于LayoutParams中的 match_parenet和具体数值这两种模式
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* 子控件可随意大小,但不可大于父控件大小,对应于LayoutParams中的 wrap_content
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* 根据提供的测量模式以及大小创建 measure specification
*/
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

小白科普:<< 是移位运算,3<<30表示的是首先把3变成二进制的11然后右边补30个0所组成的一个二进制的数。

MearsureSpec和LayoutParams对应关系
系统内部是通过MeasureSpec进行View测量的,但正常情况下,都是使View指定MeasureSpec。View测量时候系统会将LayoutParams在父容器约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。因此,Measure需要由LayoutParams和父容器一起决定。

另外,对于顶级View(DecorView),其MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定。Measure一旦决定后,onMeasure中in个即可获得View的测量宽高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* DecorView中,MeasureSpec产生过程
* 根据LayoutParams划分,并产生MeasureSpec
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 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;
}

对于普通View(布局中的View),View的measure方法需要由ViewGroup传递过来
再看看ViewGroup中的measureChildWithMargins方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

上述方法,对子元素进行measure,调哟in个子元素measure之前会获取子元素的MeasureSpec。显然,子元素MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,还与View的margin以及padding有关(具体需要研究ViewGroup的getChildMeasureSpec方法)。

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
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} 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;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

上述方法,主要根据父容器的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeasureSpec。
另外注意,子元素可用大小为父容器尺寸减去padding。

View工作流程

measure 确定View宽高
layout确定View最终宽高和四个顶点位置
draw将View绘制到屏幕

View的生命周期与工作流程

View生命周期示意图
View生命周期

View工作流程示意图
View工作流程

探究Measure过程

两种情况,若只是一个原始的View,通过measure方法就完成了测量过程;如果是ViewGroup,除了完成自身的measure过程,还需要遍历子元素的measure方法,各个子元素递归去执行这个部分。(如上面的示意图所述)

View的measure方法是一个final类型的方法 - 意味着子类不能重写该方法,因此仔细研究onMeasure方法的实现效果会更好。

这里贴出View中Measure方法,有部分注释,供有兴趣的读者阅读研究。

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
/**
* View中的Measure方法
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
//先判断当前Mode是不是特例LAYOUT_MODE_OPTICAL_BOUNDS
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
//如果自定义View重写了onMeasure方法而没有调用setMeasureDimension()方法,将会在这里抛出异常
//判断原理:通过解析状态位mPrivateFlags,setMeasureDimension()方法会将mPrivateFlags设置为已计算状态(PFLAG_MEASURED_DIMENSION_SET),只需要检查mPrivateFlags是否含有PFLAG_MEASURED_DIMENSION_SET即可。
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//计算出的key作为键,量算结果作为值,将该键值对放入成员变量mMeasureCache中,实现本次计算结果的环缓存
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

好啦,接下来,继续研究onMeasure

1
2
3
4
5
6
7
/**
* View中的onMeasure方法
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

setMeasuredDimension()方法会设置View宽高测量值,接下来进一步深入,研究getDefaultSize()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* View中的getDefaultSize方法
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED://一般是系统内部的测量过程,重点注意另外两种模式
result = size;
break;
case MeasureSpec.AT_MOST://这两种模式都是做一样的事情
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

getDefaultSize方法直接就是根据测量模式返回measureSpec中的specSize,而这个specSize就是View测量后的大小

注意:View测量后大小 与 View最终大小 需要区分,是两个东西,因为View最终大小是在layout阶段确定的,但两者几乎所有情况都是相等的

接下来,再继续探究getDefaultSize方法的第一个参数,从onMeasure方法中可知,该参数来源于下面两个方法

1
2
3
4
5
6
7
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

上述两个方法实现原理都一致,判断有没有背景,如果有,返回两者较大的宽高,没有则返回自己的宽高(android:minwith这个属性指定的值)。

那么,问题来了,背景最小宽高原理是什么?

1
2
3
4
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

上述代码中可见,Drawable的原始宽度,如果没有原始宽度,则返回0。

小白科普:ShapeDrawable无原始宽高,而BimapDrawable有原始宽高(即图片尺寸)

再谈谈ViewGroup的measure过程
主要区别:

  1. 除了完成自己measure过程还要遍历调用子元素的measure方法,各个子元素再递归执行该过程。
  2. ViewGroup是抽象类,没有重写View的onMeasure方法,而是提供了一个measureChildren的方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
    final View child = children[i];
    if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
    measureChild(child, widthMeasureSpec, heightMeasureSpec);
    }
    }
    }
    protected void measureChild(View child, int parentWidthMeasureSpec,
    int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
    mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
    mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

View在measure过程中会对每一个子元素进行measure。
再细说下,measureChild方法的思路:

  1. 取出子元素的LayoutParams
  2. 通过getChildMeasureSpec离开创建子元素的MeasureSpec
  3. 将MeasureSpec传递给View的Measure方法进行测量。

问题:为什么ViewGroup不像View一样对其onMeasure方法做统一实现?
因为不同的ViewGroup子类会有不同的特性,因此其中的onMeasure细节不相同。

获取View宽高方法不当,可能会获取错误。
原因:View的measure过程Activity生命周期方法执行顺序是不确定的,无法保证Activity执行了onCreate、onStart、onReasume时,View测量完毕。

如果View还没有完成测量,则获取的宽高会是0
给出四种方法解决:

  1. onWindowFocusChanged - 该方法被调用时候,View已经测量完毕,能够正确获取View宽高。
    注意:该方法会被调用多次,Activity窗口得到焦点与失去焦点时均会被调用一次(继续执行,暂停执行)。
    1
    2
    3
    4
    5
    6
    7
    8
    public void onWindowFocusChanged(boolean hasWindowFocus) {
    super.onWindowFocusChanged(hasWindowFocus);
    if(hasWindowFocus){
    //获取宽高
    int with = view.getMeasuredWidth();
    int height = view.getMeasuredHeight();
    }
    }
  • view.post(runnable)
    通过post将一个runnable投递到消息队列队尾,等待Looper调用此runnable时,View已初始化完毕。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    protected void onStart(){
    super.onStart();
    view.post(new Runnable(){
    @override
    public void run(){
    //获取宽高
    int with = view.getMeasuredWidth();
    int height = view.getMeasuredHeight();
    }
    })
    }
  1. ViewTreeObserver
    该类有众多回调接口,其中的OnGlobalLayoutListener接口,当View树状态发生变化或者View树内部的View的可见性发生改变,该方法都会被回调,利用此特性,可获得宽高。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    protected void onStart(){
    super.onStart();
    viewTreeObserver observer = view.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new OnGlobalListener(){
    public void onGlobalLayout(){
    view.getViewTreeObserver().removeGlobalOnlayoutListener(this);
    //获取宽高
    int with = view.getMeasuredWidth();
    int height = view.getMeasuredHeight();
    }
    })
    }
  • view.measure(int widthMeasureSpec,int heightMeasureSpec)
    手动对View进行获取,根据View的LayoutParams不同,而采取不同手段。(因不常用,这里就不详细说明)

探究layout过程

layout主要作用是ViewGroup用来确定子元素位置(递归)。

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
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//设定四个顶点位置
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
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;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

layout方法流程:

  1. setFrame方法设定View四个顶点位置
  2. 调用onLayout方法,父容器确定子元素位置
    另外,与onMeasure方法相似,onLayout方法也是各不相同的。

onLayout方法(LinearLayout、RelativeLayout等基本控件可自行尝试研究下)

探究draw过程

draw主要作用是将View绘制到屏幕上
绘制过程:

  1. 绘制背景(background.draw(canvas))
  2. 绘制自己(onDraw)
  3. 绘制children(dispatchDraw)
  4. 绘制装饰(onDrawScrollBars)
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
/**
* View中的draw方法
*/
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) {
drawBackground(canvas);
}
// 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);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(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);
p.setShader(fade);
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);
p.setShader(fade);
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);
p.setShader(fade);
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);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}

View的绘制过程传递是通过dispatchDraw来实现,dispatchDraw会遍历所有子元素的draw方法,另外,View还有一个特殊的方法setWillNotDraw

1
2
3
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

setFlags - 该方法可设置优化标记
如果View不需要绘制任何内容,那么将设置标记为true,系统会相应优化。默认情况下,View不启用这个标记位,但ViewGroup会默认启动该优化标记。
而实际开发意义:当我们自定义控件继承于ViewGroup并且本身不具备绘制功能,则开启标记。而如果明确知道一个ViewGroup需要通过onDraw来绘制内容时候,则需要显式关闭WILL_NOT_DRAW这个标记位。

感谢您的阅读,希望文章对您有所帮助