我是靠谱客的博主 落寞中心,这篇文章主要介绍Android 自定义控件基础:measure过程,现在分享给大家,希望可以做个参考。

measure过程要分为两种情况:如果是原始的View,需要通过measure 方法就完成其测量过程;如果是ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。

ViewRootImpl

View 的整个绘制流程的启动入口可以从 ViewRootImpl 的 performTraversals 方法开始看。performTraversals 方法调用了performMeasure()方法。

复制代码
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
// ViewRootImpl 的源码 private void performTraversals() { .... if (!mStopped || mReportNextDraw) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || dispatchApplyInsets || updatedConfiguration) { // mWidth/mHeight:屏幕的宽/高 lp.width/lp.height:DecorView 的宽高 // 得到DecorView 的MeasureSpec int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); // 测量过程开始 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ..... } } .... } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { // mView(DecorView) 调用View#measure方法 进行测量宽/高 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } 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过程

View 的measure 过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View 的measure 方法中会去调用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
// View 的源码 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { .... final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec; final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); //如果上一次的测量规格和这次不一样,则条件满足,重新测量视图View的大小 if (forceLayout || needsLayout) { 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; } } .... mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }

从上面的代码可以看出,首先判断和上次测量的MeasureSpec是否相等,如果不相等就重新测量。measure() 方法调用了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
30
31
32
33
34
35
36
37
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // setMeasuredDimension方法 设置View宽/高的测量值 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // 根据View默认大小的宽高和父View传递的测量规格重新计算View的测量宽高 public static int getDefaultSize(int size, int measureSpec) { // size代表view的默认尺寸大小 int result = size; // measureSpec代表父容器提供的子View的MeasureSpec int specMode = MeasureSpec.getMode(measureSpec); // specSize 是View 测量后的大小 int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } // result代表返回的view的尺寸大小 return result; } protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }

从以上源码来看,如果View没有设置背景,那么View的宽度为mMinWidth,而mMinWidth对应于android:minWidth 这个属性所指定的值,因此View的宽度为android:minWidth属性所指定的值。android:minWidth属性如果不指定,那么mMinWidth则默认为0;如果View指定了背景,则返回android:minWidth和背景的最小宽度这两者中的最大值,getSuggestedMinimumWidth 和 getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情况下的测量宽/高。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
// View 源码 public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; } public int getMinimumHeight() { final int intrinsicHeight = getIntrinsicHeight(); return intrinsicHeight > 0 ? intrinsicHeight : 0; }

从以上的代码来看,返回的是Drawable的原始宽度/高度,前提是这个Drawable 有原始宽/高度,否则就返回0;举个例子,ShapeDrawable 无原始宽/高;而BitmapDrawable有原始宽/高(图片的尺寸)。

Question: 直接继承View 的自定义控件需要重写onMeasure 方法并设置wrap_content 时自身大小,否则在布局中使用 wrap_content就相当于使用 match_parent?

Why: 如果View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽/高等于specSize;这种情况下View的specSize是parentSize,而parentSize是父容器中目前可以使用大小,也就是父容器当前剩余的空间大小。View的宽/高就等于父容器当前剩余的空间大小。

Answer :

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec,heightMeasureSpec); int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec); if(widthSpecMode==MeasureSpec.AT_MOST &&heightSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(mWidth,mHeight); }else if(widthSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(mWidth,heightSpecSize); }else if(heightSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(widthSpecSize,mHeight); } }

在上面的代码中,只需要给View指定一个默认的内部宽/高(mWidth 和mHeight),并在wrap_content时设置此宽/高即可。对于非wrap_content时,用系统的测量值即可。至于这个默认的内部宽/高的大小如何指定,这个没有固定的依据,根据需要灵活指定即可

ViewGroup 的measure 过程

对于ViewGroup来说,除了完成自己的measure 过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。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
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
// ViewGroup 源码 // 遍历子View & 调用measureChild()进行下一步测量 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()进行下一步的测量 measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { // 获取子视图的布局参数 final LayoutParams lp = child.getLayoutParams(); // 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); // 将计算好的子View的MeasureSpec值传入measure(),进行最后的测量 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); // specSize 父容器的大小 int specSize = MeasureSpec.getSize(spec); // padding 父容器中已占用的空间大小 // size 子元素可用空间大小 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; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }

从以上的代码来看,ViewGroup在measure时,会对每一个子元素会调用measureChild方法进行测量。measureChild方法就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法进行测量。

下面具体分析实例:LinearLayout 源码measureHorizontal

复制代码
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
// LinearLayout 源码 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { .... // 获取垂直方向上的子View个数 final int count = getVirtualChildCount(); .... for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); // 子View不可见,直接跳过该View的measure过程, // getChildrenSkipCount()返回值恒为0 if (child.getVisibility() == GONE) { i += getChildrenSkipCount(child, i); continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); // 记录子View是否有weight属性设置,用于后面判断是否需要二次measure totalWeight += lp.weight; final boolean useExcessSpace = lp.width == 0 && lp.weight > 0; if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) { if (isExactly) { mTotalLength += lp.leftMargin + lp.rightMargin; } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin); } ... }else{ ... final int usedWidth = totalWeight == 0 ? mTotalLength : 0; // 该方法内部最终会调用measureChildren(),从而遍历所有子View & 测量 measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth, heightMeasureSpec, 0); if (isExactly) { mTotalLength += childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } } } if (useLargestChild && (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) { mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); ... if (child.getVisibility() == GONE) { i += getChildrenSkipCount(child, i); continue; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); if (isExactly) { mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + largestChildWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } } } // mTotalLength用于存储LinearLayout在水平方向的长度 mTotalLength += mPaddingLeft + mPaddingRight; int widthSize = mTotalLength; int widthSizeAndState = resolveSizeAndState(widthSize, widthMeasureSpec, 0); widthSize = widthSizeAndState & MEASURED_SIZE_MASK; maxHeight += mPaddingTop + mPaddingBottom; // Check against our minimum height maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK), resolveSizeAndState(maxHeight, heightMeasureSpec, (childState<<MEASURED_HEIGHT_STATE_SHIFT))); }

测量流程:
在这里插入图片描述

总结

  • 对于 View 来说,其 MeasureSpec 是由其父容器 ViewGroup 的 MeasureSpec 和 View 自身的 LayoutParams 来共同决定的。
  • 对于DecorView,其MearsureSpec是窗口尺寸和其自己的LayoutParames共同决定。

最后

以上就是落寞中心最近收集整理的关于Android 自定义控件基础:measure过程的全部内容,更多相关Android内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(93)

评论列表共有 0 条评论

立即
投稿
返回
顶部