0%

View绘制流程

在Activity显示到界面流程的最后一步ViewRoot.setView中有个requestLayout方法,这个方法其实就是请求绘制view。

1 View绘制的整体流程

1.1 ViewRoot.requestLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void requestLayout() {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}

public void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
sendEmptyMessage(DO_TRAVERSAL);
}
}

case DO_TRAVERSAL:

performTraversals();

break;

1.2 ViewRoot.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
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
public final class ViewRoot extends Handler implements ViewParent,  
View.AttachInfo.Callbacks {
......

View mView;
......

boolean mLayoutRequested;
boolean mFirst;
......
boolean mFullRedrawNeeded;
......

private final Surface mSurface = new Surface();
......

private void performTraversals() {
......

final View host = mView;

if (mFirst) {
fullRedrawNeeded = true;
mLayoutRequested = true;

DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
attachInfo.mSurface = mSurface;
attachInfo.mUse32BitDrawingCache = PixelFormat.formatHasAlpha(lp.format) ||
lp.format == PixelFormat.RGBX_8888;
attachInfo.mHasWindowFocus = false;
attachInfo.mWindowVisibility = viewVisibility;
attachInfo.mRecomputeGlobalAttributes = false;
attachInfo.mKeepScreenOn = false;
viewVisibilityChanged = false;
mLastConfiguration.setTo(host.getResources().getConfiguration());
host.dispatchAttachedToWindow(attachInfo, 0);
}else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(TAG,
"View " + host + " resized to: " + frame);
fullRedrawNeeded = true;
mLayoutRequested = true;
windowResizesToFitContent = true;
}
}
......

mTraversalScheduled = false;
......
boolean fullRedrawNeeded = mFullRedrawNeeded;
boolean newSurface = false;
......

if (mLayoutRequested) {
......

host.measure(childWidthMeasureSpec, childHeightMeasureSpec);

.......
}

......

int relayoutResult = 0;
if (mFirst || windowShouldResize || insetsChanged
|| viewVisibilityChanged || params != null) {
......

boolean hadSurface = mSurface.isValid();
try {
......

relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
......

if (!hadSurface) {
if (mSurface.isValid()) {
......
newSurface = true;
fullRedrawNeeded = true;
......
}
}
......
} catch (RemoteException e) {
}

......
}

final boolean didLayout = mLayoutRequested;
......

if (didLayout) {
mLayoutRequested = false;
......

host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);

......
}

......

mFirst = false;
......

boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();

if (!cancelDraw && !newSurface) {
mFullRedrawNeeded = false;
draw(fullRedrawNeeded);

......
} else {
......

// Try again
scheduleTraversals();
}
}

......
}

1)如果是第一次进行View遍历,则对mAttachInfo变量初始化,然后调用host.dispatchAttachedToWindow传递给每一个View,这样View中可以通过这个变量调用重新绘制。
2)如果不是第一次看是否布局大小有没有变化,有则将fullRedrawNeeded、mLayoutRequested、windowResizesToFitContent赋值为true。fullRedrawNeeded代表要全部重新绘制,mLayoutRequested重新指定视图位置。
3)如果要重新布局(mLayoutRequested为true),要重新layout必须重新mesure,所以需要重新计算视图大小host.measure。
4)当视图大小变化或者视图visible发生变化都需要请求WindowManagerSerivce重新分配窗口大小。
5)如果需要重新布局调用host.layout
6)如果不中断draw则调用draw方法,否则重新调用一次performTraversals流程。

2.View的mesure流程

2.1 View.mesure

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
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {

// first clears the measured dimension flag
mPrivateFlags &= ~MEASURED_DIMENSION_SET;

if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
}

// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);

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

mPrivateFlags |= LAYOUT_REQUIRED;
}

mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}

1)View.mesure 是一个final方法,子类不可以重写,所以子类只能重写onMeasure。widthMeasureSpec和heightMeasureSpec对应的是视图允许的最大宽和高,measureSpec是int类型,该值有高32位和低16位组成,高32位代表的specMode
,低16为表示的是specSize。
2)specMode有三种类型。MeasureSpec.UNSPECIFIED大小不确定,根据自身大小确定的,也就是WRAP_CONTENT;MeasureSpec.AT_MOST占有父布局最大的大小,也就是MATCH_PARENT;MeasureSpec.EXACTLY大小确定,比如直接写100dp。
3)specSize就是确定父View能给的最大大小的值。
4)ViewRoot类的成员变量mPrivateFlags的类型为int,如果它的某一个位的值不等于0,那么就隐含着当前视图有一个相应的操作在等待执行中。ViewRoot类的另外两个成员变量mOldWidthMeasureSpec和mOldHeightMeasureSpec用来保存当前视图上一次可以获得的最大宽度和高度。
当ViewRoot类的成员变量mPrivateFlags的FORCE_LAYOUT位不等于0时,就表示当前视图正在请求执行一次布局操作,这时候函数就需要重新测量当前视图的宽度和高度。此外,当参数widthMeasureSpec和heightMeasureSpec的值不等于ViewRoot类的成员变量mldWidthMeasureSpec和mOldHeightMeasureSpec的值时,就表示当前视图上一次可以获得的最大宽度和高度已经失效了,这时候函数也需要重新测量当前视图的宽度和高度。

2.2 FrameLayout.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
33
34
35
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();

int maxHeight = 0;
int maxWidth = 0;

// Find rightmost and bottommost child
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
}

// Account for padding too
maxWidth += mPaddingLeft + mPaddingRight + mForegroundPaddingLeft + mForegroundPaddingRight;
maxHeight += mPaddingTop + mPaddingBottom + mForegroundPaddingTop + mForegroundPaddingBottom;

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

// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}

setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
resolveSize(maxHeight, heightMeasureSpec));
}

1)首先调用measureChildWithMargins计算子View的大小。
2)用maxWidth和maxHeight计算子View需要的总大小。
3) 然后保存当前View的大。

2.3 View.measureChildWithMargins

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
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);
}

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 = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

这里是测量子View的大小,根据父View和子View的大小和属性,确定子View的大小,然后调用child.mesure方法。

2.4 View.setMeasuredDimension

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static int resolveSize(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:
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;

mPrivateFlags |= MEASURED_DIMENSION_SET;
}

确定自己的大小值记录在mMeasuredWidth和mMeasuredHeight中。

3 View的Layout过程

3.1 View.layout

1
2
3
4
5
6
7
8
9
10
11
12
public final void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}

onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
}

3.2 View.setFrame

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
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;

if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;

// Remember our drawn bit
int drawn = mPrivateFlags & DRAWN;

// Invalidate our old position
invalidate();


int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;

mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;

mPrivateFlags |= HAS_BOUNDS;

int newWidth = right - left;
int newHeight = bottom - top;

if (newWidth != oldWidth || newHeight != oldHeight) {
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
}

if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
mPrivateFlags |= DRAWN;
invalidate();
}

// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;

mBackgroundSizeChanged = true;
}
return changed;
}

当View的大小发生变化的时候才会重新设置大小

3.3

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
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();

final int parentLeft = mPaddingLeft + mForegroundPaddingLeft;
final int parentRight = right - left - mPaddingRight - mForegroundPaddingRight;

final int parentTop = mPaddingTop + mForegroundPaddingTop;
final int parentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom;

mForegroundBoundsChanged = true;

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();

final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();

int childLeft = parentLeft;
int childTop = parentTop;

final int gravity = lp.gravity;

if (gravity != -1) {
final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

switch (horizontalGravity) {
case Gravity.LEFT:
childLeft = parentLeft + lp.leftMargin;
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = parentRight - width - lp.rightMargin;
break;
default:
childLeft = parentLeft + lp.leftMargin;
}

switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
}

child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}

FrameLayout类的成员变量mPaddingLeft、mPaddingRight、mPaddingTop、mPaddingBottom和mForegroundPaddingLeft、mForegroundPaddingRight、mForegroundPaddingTop、mForegroundPaddingBottom的含义我们在前面分析Android应用程序窗品的测量过程时已经解释过了,它们描述的是当前视图的内边距,而参数left、top、right和bottom描述的是当前视图的外边距,即它与父窗口的边距。通过上述这些参数,我们就可以得到当前视图的子视图所能布局在的区域。
FrameLayout类的成员函数onLayout通过一个for循环来布局当前视图的每一个子视图。如果一个子视图child是可见的,那么FrameLayout类的成员函数onLayout就会根据当前视图可以用来显示子视图的区域以及它所设置的gravity属性来得到它在应用程序窗口中的左上角位置(childeLeft,childTop)。
当一个子视图child在应用程序窗口中的左上角位置确定了之后,再结合它在前面的测量过程中所确定的宽度width和高度height,我们就可以完全地确定它在应用程序窗口中的布局了,即可以调用它的成员函数layout来设置它的位置和大小了。

4. View.draw 过程

4.1 ViewRoot.draw
这一步更多的是底层的东西,准备画布

4.2DecorView.draw

1
2
3
4
5
6
7
public void draw(Canvas canvas) {
super.draw(canvas);

if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}

DecorView类的成员函数draw首先调用父类FrameLayout的成员函数draw来绘制它的UI内容,然后再检查它是否设置了菜单背景,即成员变量mMenuBackground的值是否不等于null。如果不等于null的话,那么就会调用它所指向的一个Drawable对象的成员函数draw来在画布canvas上绘制这个菜单背景。

4.2 View.draw
调用dispatchDraw

4.3 ViewGroup. dispatchDraw
遍历子view,调用子view.draw

特别推荐,将的非常详细。
Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析

Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析

View 绘制流程