0%

Android-View绘制原理

概述

注:本文基于Android 10源码,为了文章的简洁性,引用源码的地方可能有所删减。

Android-Window机制原理Android-Choreographer原理 中了解到,无论是通过 WindowManager.addView 方法添加View,还是调用 View.invalidate 或 View.requestLayout 等方法更新 View 都会走到 ViewRootImpl.scheduleTraversals 方法,在这个方法中有一段代码:mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null),其中mChoreographer是Choreographer实例,Choreographer用来处理Vsync信号,当Vsync信号到来时,mTraversalRunnable会被执行,mTraversalRunnable是一个Runnable实例,其run方法最终会调用 ViewRootImpl.performTraversals 方法。

因此当Vsync信号来临时,会调用 ViewRootImpl.performTraversals 方法执行View的绘制流程。以startActivity为例,WindowManager.addView 添加的View是DecorView类型。以下ViewRootImpl缩写为VRImpl。

DecorView

在 startActivity.setContentView 中会创建DecorView实例,然后在 AT.handleResumeActivity 中会调用 WindowManager.addView 方法添加DecorView。在这里给出 DecorView 的继承关系:

1
2
3
4
View (android.view)
ViewGroup (android.view)
FrameLayout (android.widget)
DecorView (com.android.internal.policy)

VRImpl.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
// ViewRootImpl
private void performTraversals() {
// 在 startActivity 中,这里的 mView 是 DecorView 实例
final View host = mView;
mIsInTraversal = true;
Rect frame = mWinFrame;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// 此处可能会调用 performMeasure 方法
windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
}
if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// 处理 Surface, Render, WMS.relayout 等相关的逻辑
// ...
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
if (!mStopped || mReportNextDraw) {
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 如果 lp.horizontalWeight 或 lp.verticalWeight 大于 0 则重新调用 performMeasure 测量
// ...
layoutRequested = true;
}
}
}
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
if (!cancelDraw && !newSurface) {
performDraw();
}
mIsInTraversal = false;
}

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
boolean windowSizeMayChange = false;
boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just stretch to fill
// the entire width of the screen to display one line of text.
// First try doing the layout at a smaller size to see if it will fit.
// ...
}

if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}

VRImpl.performMeasure

1
2
3
4
5
6
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

MeasureSpec

在解析measure过程之前,先看一下MeasureSpec这个类,MeasureSpec是父控件提供给子View的一个参数,作为设定自身大小参考,这只是个参考,子View具体要多大,还是它自己说了算。先看下MeasureSpec的构成,MeasureSpec由size和mode组成,mode包括三种,UNSPECIFIED、EXACTLY、AT_MOST,size就是配合mode给出的参考尺寸。

1
MeasureSpec(32位) = mode(2位) + size(30位)

具体关系如下:

mode Description Note
UNSPECIFIED 父控件对子控件不加任何约束,子View可以取任意尺寸大小 一般是由父控件自身的特性决定的。比如ScrollView和ListView等,它的子View可以随意设置大小,size一般没什么意义。
EXACTLY 父控件为子View指定一个确切大小,希望子View按照自己给定的尺寸来处理 一般是父控件根据自身的MeasureSpec跟子View的布局参数来确定的,这种情况下size有个确定值。
AT_MOST 父控件为子元素指定最大参考尺寸,希望子View的尺寸不要超过这个尺寸 这种模式也是父控件根据自身的MeasureSpec跟子View的布局参数来确定的,一般是子View的布局参数采用wrap_content的时候。

看一下MeasureSpec的几个API:

1
2
3
4
5
6
7
8
// 1. 获取测量模式(Mode)
int specMode = MeasureSpec.getMode(measureSpec)

// 2. 获取测量大小(Size)
int specSize = MeasureSpec.getSize(measureSpec)

// 3. 通过Mode 和 Size 生成新的SpecMode
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

子View的MeasureSpec值根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具体计算逻辑封装在 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
68
69
70
71
72
73
74
75
76
77
78
79
80
// View.java
static boolean sUseZeroUnspecifiedMeasureSpec = false;

public View(Context context) {
sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M;
}

// ViewGroup.java
/**
* 根据父View的MeasureSpec & 子View的布局参数LayoutParams,计算子View的MeasureSpec
* @param spec 父view的MeasureSpec
* @param padding 父view的padding, margins
* @param childDimension 子视图的LayoutParams(宽 or 高)
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 父View的测量模式
int specMode = MeasureSpec.getMode(spec);
// 父View的size
int specSize = MeasureSpec.getSize(spec);
// 通过父View计算出的子view = 父大小 - 边距(父View要求的大小,但子view不一定用这个值)
int size = Math.max(0, specSize - padding);
// 子view想要的实际大小和模式
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;
}
// noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

该方法得到的子View的MeasureSpec内容如下:

getChildMeasureSpec

其中UNSPECIFIED的resultSize根据Android版本的不同,可以为0或size。getChildMeasureSpec 方法可以重写,在 RelativeLayout 中就实现了自定义的 getChildMeasureSpec 方法。

Activity的根布局是DecorView,传递给DecorView的MeasureSpec是根据Window的LayoutParams(Theme/代码动态控制等)来确定的,也就是说最初的MeasureSpec是直接根据Window的属性构建的。上面调用performMeasure方法时传入的MeasureSpec参数,可以看到是通过这两行代码指定的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ViewRootImpl
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

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.measure

以startActivity为例,这里的mView为DecorView类型,因此这里调用了其父类的View.measure方法(该方法是final类型,只有父类–View有这个方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// ...
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
if (forceLayout || needsLayout) {
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
// ...
}
}
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

接着调用了DecorView的onMeasure方法。

DecorView.onMeasure

在看这个方法之前,先理解几个Theme中Window的配置参数:

1
2
3
4
5
6
7
8
9
10
11
12
<resources>
<style name="AppDialogTheme">
<item name="windowFixedWidthMinor">80%</item>
<item name="windowFixedHeightMajor">50%</item>

<item name="windowFixedWidthMajor">80%</item>
<item name="windowFixedHeightMinor">60%</item>

<item name="windowMinWidthMajor">50%</item>
<item name="windowMinWidthMinor">50%</item>
</style>
</resources>

含义如下:

  • windowFixedWidthMinor: 竖屏下Window的固定宽度,可以为固定值或百分比;
  • windowFixedHeightMajor: 竖屏下Window的固定高度,可以为固定值或百分比;
  • windowFixedWidthMajor: 横屏下Window的固定宽度,可以为固定值或百分比;
  • windowFixedHeightMinor: 横屏下Window的固定高度,可以为固定值或百分比;
  • windowMinWidthMajor: 横屏下Window的最小宽度,可以为固定值或百分比;
  • windowMinWidthMinor: 竖屏下Window的最小宽度,可以为固定值或百分比;

以上参数当mode为AT_MOST时才起作用,在DecorView.onMeasure方法中需要处理这几种参数,因为它是根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
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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
// 是否是竖屏
final boolean isPortrait = getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;

final int widthMode = getMode(widthMeasureSpec);
final int heightMode = getMode(heightMeasureSpec);

boolean fixedWidth = false;
if (widthMode == AT_MOST) { // 当mode为AT_MOST才起作用
final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor;
if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
// 获取配置的width值
final int w;
if (tvw.type == TypedValue.TYPE_DIMENSION) {
w = (int) tvw.getDimension(metrics);
} else if (tvw.type == TypedValue.TYPE_FRACTION) {
w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
} else {
w = 0;
}
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (w > 0) {
// 取最小值
widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(w, widthSize), EXACTLY);
fixedWidth = true;
} else {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize - mFloatingInsets.left - mFloatingInsets.right, AT_MOST);
}
}
}

if (heightMode == AT_MOST) {
// 类似上面处理流程,处理mFixedHeightMajor/mFixedHeightMinor
}

// Returns the outsets, which areas of the device that aren't a surface, but we would like to treat them as such.
// ...

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int width = getMeasuredWidth();
boolean measure = false;

widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);

if (!fixedWidth && widthMode == AT_MOST) {
// fixedWidth没有生效 & AT_MOST
final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
if (tv.type != TypedValue.TYPE_NULL) {
final int min;
if (tv.type == TypedValue.TYPE_DIMENSION) {
min = (int)tv.getDimension(metrics);
} else if (tv.type == TypedValue.TYPE_FRACTION) {
min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth);
} else {
min = 0;
}
if (width < min) {
// 第一次onMeasure后得到的width小于设置的最小width
widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
measure = true;
}
}
}

// TODO: Support height?

if (measure) {
// 重新measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

DecorView.onMeasure方法中,当第一次测量的时候(AT_MOST)不满足最小宽度,则需要使用给定的最小宽度重新测量。上面的super.onMeasure(widthMeasureSpec, heightMeasureSpec)调用的是DecorView父类 FrameLayout 的 onMeasure 方法。

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
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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// 如果FrameLayout宽高有不是 EXACTLY 的,需要再测量一次match_parent的子视图
final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();

int maxHeight = 0;
int maxWidth = 0;
int childState = 0;

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 测量子View的长宽(包含父View的padding和子View的margin)
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}

// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

// 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(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));

// 绘制宽或高为match_parent的View
count = mMatchParentChildren.size();
if (count > 1) {
// 有超过1个子View设置了match_parent属性,需要重新测量FrameLayout的宽高
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
// 设置子view的宽度为当前FrameLayout测量后的宽度
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin, lp.width);
}

final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin, lp.height);
}

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}

当 FrameLayout 的宽或高不为 EXACTLY,且同时有两个及以上的子 View 属性为 match_parent 时,则这些子 view 会重新 measure。因为它们需要依赖父布局的尺寸,若父布局的测量模式不是指明明确的尺寸,则将该child添加至待测列表中,遍历完child设置了FrameLayout自身宽高后重新测量。

在 FrameLayout 的测量中会遍历子View测量,当遇到ViewGroup时会根据其类型(FrameLayout, LinearLayout 等)调用其onMeasure方法,当遇到View时也会根据其类型(TextView等)直接调用其onMeasure方法。

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
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

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

ViewGroup.measureChildWithMargins

1
2
3
4
5
6
7
8
9
10
11
12
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
/* 父View的padding + 子View的margin */
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方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

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
25
26
27
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;

measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}

public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}

小结

performMeasure 流程:

  • View 是自定义 View: View.measure -> (CustomView) View.onMeasure
  • View 是自定义 ViewGroup: View.measure -> (CustomViewGroup) View.onMeasure -> ViewGroup.measureChildWithMargins -> View.measure -> ...

只有 View 中存在 measure 方法:

1
2
3
4
5
// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// ...
onMeasure(widthMeasureSpec, heightMeasureSpec);
}

VRImpl.performLayout

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
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
mInLayout = true;
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// we need to clear those flags and do a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false);
if (validLayoutRequesters != null) {
mHandlingLayoutInLayoutRequest = true;

int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
// 对在layout期间调用了requestLayout()方法的View,在host.layout结束后重新requestLayout
view.requestLayout();
}
// 调用performMeasure重新测量
measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
// 重新layout host View
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

mHandlingLayoutInLayoutRequest = false;

// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// View.post
// 将第二次请求发送到下一帧
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
view.requestLayout();
}
}
});
}
}
}
mInLayout = false;
}

这里如果在 performLayout 期间有View调用了 requestLayout 方法,那么在 host.layout 完成后需要清空相关 flag(PFLAG_FORCE_LAYOUT),并且重新走 request/measure/layout 的流程。如果还存在需要 layout 的请求,则将其放到下一帧处理。

由于host是 DecorView 实例,因此看看 ViewGroup.layout 方法

ViewGroup.layout

1
2
3
4
5
@Override
public final void layout(int l, int t, int r, int b) {
// ...
super.layout(l, t, r, b);
}

没啥好看的,直接看 View.layout 方法。

View.layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
// 如果需要测量则先调用onMeasure方法
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;
// OnLayoutChangeListener 回调
}
// 处理focus相关
}

DecorView.onLayout

1
2
3
4
5
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// ...
}

FrameLayout.onLayout

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
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();

final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
// 当前子View不是GONE则layout
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();

int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}

final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
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);
}
}
}

这个方法主要是通过padding和margin计算出子 View 的边界,然后调用子 View 的 layout 方法,具有 layout 方法的只有 View 和 ViewGroup,因此根据其具体类型(LinearLayout, TextView等)重复上述流程调用其 onLayout 方法。

小结

performLayout 流程:

  • View 是自定义 View: View.layout -> (CustomView) View.onLayout
  • View 是自定义 ViewGroup: View.layout -> (CustomViewGroup) ViewGroup.onLayout -> View.layout -> ...

View 和 ViewGroup 中都存在 layout 方法,ViewGroup 子类不能复写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// View.java
public void layout(int l, int t, int r, int b) {
// ...
onLayout(changed, l, t, r, b);
}

// ViewGroup.java
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}

VRImpl.performDraw

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
private void performDraw() {
// ...
mIsDrawing = true;
try {
boolean canUseAsync = draw(fullRedrawNeeded);
// ...
} finally {
mIsDrawing = false;
}
// ...
}

private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return false;
}
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
// 设置需要完全重draw
fullRedrawNeeded = true;
}
final float appScale = mAttachInfo.mApplicationScale;
final Rect dirty = mDirty;
if (fullRedrawNeeded) {
// 需要完全reDraw
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}

if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
// ...
// 硬件加速渲染
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
} else {
// Software渲染
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}

if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
return useAsyncReport;
}

这里的绘制方式有硬件加速Software渲染,关于选择哪种方式,可以在 ViewRootImpl.setView 方法中找到:

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
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
if (view instanceof RootViewSurfaceTaker) {
mSurfaceHolderCallback = ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
mSurfaceHolder.addCallback(mSurfaceHolderCallback);
}
}
if (mSurfaceHolder == null) {
// 开启硬件加速
enableHardwareAcceleration(attrs);
}
}
// ...
}
}

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) {
super(context);
// 由之前创建DecorView的方法可知传入的featureId = -1
mFeatureId = featureId;
// ...
}

public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
}
}

public class PhoneWindow extends Window implements MenuBuilder.Callback {

// 设置mTakeSurfaceCallback的方法
@Override
public void takeSurface(Callback2 callback) {
mTakeSurfaceCallback = callback;
}
}

// 在源码中只找到 NativeActivity 设置了 mTakeSurfaceCallback
public class NativeActivity extends Activity implements SurfaceHolder.Callback2, InputQueue.Callback, OnGlobalLayoutListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().takeSurface(this);
// ...
}
}

查看源码,DecorView 实现了 RootViewSurfaceTaker 接口,因此当DecorView满足一定的条件则不会开启硬件加速(设置了mTakeSurfaceCallback),而其它未实现 RootViewSurfaceTaker 接口的 View 则会默认调用 enableHardwareAcceleration 方法通过配置去判断硬件加速是否需要开启。

无论使用软件绘制还是硬件绘制,都会调用到 View.draw 方法。软件绘制与硬件绘制的流程见: Android-Surface原理解析

View.draw

View.draw 方法分为如下步骤:

  1. Draw the background
  2. If necessary, save the canvas’ layers to prepare 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)
  7. draw the default focus highlight

部分源码如下:

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
public void draw(Canvas canvas) {
// 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);

drawAutofilledHighlight(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);

// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
return;
}

// uncommon case

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;

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

saveCount = canvas.getSaveCount();

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

canvas.restoreToCount(saveCount);

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

ViewGroup.dispatchDraw

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void dispatchDraw(Canvas canvas) {
// ...
for (...) {
drawChild(...)
}
}

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

小结

performDraw 流程:

  • View 是自定义 View: View.draw -> (CustomView) View.onDraw
  • View 是自定义 ViewGroup: View.draw -> 1. View.onDraw -> 2. (CustomViewGroup) ViewGroup.dispatchDraw -> View.draw(Canvas canvas, ViewGroup parent, long drawingTime) -> ...

View 中 draw 和 onDraw 方法都可以被复写:

1
2
3
4
5
6
7
// View.java
public void draw(Canvas canvas) {
// ...
}

protected void onDraw(Canvas canvas) {
}