在前一篇博客Android O: View的绘制流程(一): 创建和加载中, 
我们分析了系统创建和加载View的过程,这部分内容完成了View绘制的前置工作。

本文开始分析View的测量的流程。


一、绘制流程的起点 
在分析View的测量的流程前,我们先来寻找一下界面绘制流程的起点。 
当Activity启动时,会调用ActivityThread的handleLaunchActivity方法:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {............//这部分代码,会调用Activity的onCreate, 进而调用setContentView//完成上一篇博客描述的前置工作Activity a = performLaunchActivity(r, customIntent);if (a != null) {.............//重点关注该函数handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);.............} else {.............}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

我们跟进一下handleResumeActivity函数:

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {..........// 会回调Activity的onResume接口r = performResumeActivity(token, clearHide, reason);if (r != null) {final Activity a = r.activity;.........if (r.window == null && !a.mFinished && willBeVisible) {//之前已经创建出Activity对应的PhoneWindow和DecorView//将这些对象记录到ActivityRecord中r.window = r.activity.getWindow();View decor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);//得到WindowManagerImplViewManager wm = a.getWindowManager();WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;..........//如果Activity可见if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;//Activity的DecorView递交给WindowManagerwm.addView(decor, l);} else {...........}}} else if (...) {.......}.......} else {.........}
}
  • 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

从上述代码可以看出,解析完XML对应的View后, 
最终将DecorView递交给WindowManager。

我们跟进一下WindowManagerImpl中的addView函数:

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);//实际上定义于WindowManagerGlobal中mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}
  • 1
  • 2
  • 3
  • 4
  • 5

继续跟进WindowManagerGlobal中的代码:

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {............final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;//进一步调整wparams.............ViewRootImpl root;View panelParentView = null;synchronized (mLock) {..........//创建出View对应的ViewRootroot = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);try {//关联View和ViewRootImplroot.setView(view, wparams, panelParentView);} catch (RuntimeException e) {...........}}
}
  • 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

至此我们知道了,WindowManager将DecorView和对应的ViewRootImpl关联起来了。 
现在来一起看看ViewRootImpl的setView函数:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {mView = view;...........//初次布局开始requestLayout();...........}}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

容易看出ViewRootImpl与View关联后,会调用requestLayout函数, 
该函数将开启整个绘制流程。

眼见为实,我们来看看这个requestLayout函数:

    public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;//继续跟进scheduleTraversals();}}void scheduleTraversals() {//mTraversalScheduled用于限制绘制的次数if (!mTraversalScheduled) {mTraversalScheduled = true;...........//将mTraversalRunnable加入执行队列mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);...........}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

最后,我们来看看TraversalRunnable的实现:

    final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

TraversalRunnable在执行时,会调用doTraversal函数,对应代码如下:

void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;..........//开始绘制了performTraversals();..........}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

绘制的主要逻辑定义于ViewRootImpl的performTraversals中, 
该函数会遍历整个视图书,逐一绘制每个View。

performTraversals函数接近1000行左右且涉及较多琐碎的细节, 
个人感觉没有逐行解析的必要,因此我们主仅关注主要的逻辑。

实际上performTraversals的代码流程可以大致分为三个阶段,如下所示:

private void performTraversals() {.............// 测量阶段int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);............performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);............// 布局阶段performLayout(lp, mWidth, mHeight);............// 绘制阶段performDraw();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

总结一下上述整个代码的调用流程,大致如下所示: 

二、Measure阶段 
将performTraversals划分为三个阶段后,整体的逻辑就可以简化为下图: 

现在我们来看看其中Measure阶段的代码。

2.1 MeasureSpec 
在分析测量的代码前,我们先要了解一下MeasureSpec的概念。 
MeasureSpec是定义于View.java中的内部类,表示一个32位的整形值。 
它的高2位表示测量模式SpecMode,低30位表示在相应模式下的测量尺寸SpecSize。

目前SpecMode的取值可以为以下三种:

/*** Measure specification mode: The parent has not imposed any constraint* on the child. It can be whatever size it wants.*/public static final int UNSPECIFIED = 0 << MODE_SHIFT;/*** Measure specification mode: The parent has determined an exact size* for the child. The child is going to be given those bounds regardless* of how big it wants to be.*/public static final int EXACTLY     = 1 << MODE_SHIFT;/*** Measure specification mode: The child can be as large as it wants up* to the specified size.*/public static final int AT_MOST     = 2 << MODE_SHIFT;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

如注释所述: 
UNSPECIFIED 表示不指定测量模式,对应的场景是: 
父视图没有限制子试图大小,子试图可以是想要的任何尺寸。 
这种模式基本用不到。

EXACTLY 表示精确测量模式,对应的场景是: 
父视图已经指定了子试图的精确大小,此时测量值就是SpecSize的值。 
当视图的layout_width或者layout_height指定为具体的数值, 
或指定为match_parent时,该模式生效。

AT_MOST 表示最大值模式,对应的场景是: 
当视图的layout_width或layout_height指定为wrap_content时, 
子视图的尺寸可以是不超过父视图允许最大值的任何尺寸。

我们来看看前文代码中的getRootMeasureSpec函数:

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

从代码来看,根据LayoutParams的参数,getRootMeasureSpec会得到对应模式的MeasureSpec。 
其中主要用到的还是EXACTLY和AT_MOST模式。

对于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定; 
对于普通的View,它的MeasureSpec由父视图的MeasureSpec和自身的LayoutParams共同决定。

2.2 measure 
了解完MeasureSpec后,我们来看看performMeasure函数:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {........//实际上调用的还是View的measure接口mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);........
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们跟进View的measure函数:

//参数为父ViewGroup对当前View的约束信息
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {//当前View为ViewGroup且设置为视觉边界布局模式时,才返回trueboolean optical = isLayoutModeOptical(this);//当前View与父容器的模式不同时,需要调整MeasureSpecif (optical != isLayoutModeOptical(mParent)) {............widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);}// Suppress sign extension for the low bytes// 计算key值, 用于判断是否有缓存及作为存储键值long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);//判断是否需要强制重新布局//例如View调用requestLayout时,会在mPrivateFlags添加该标记final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;// 以下其实就是判断是否需要重新布局// Optimize layout by avoiding an extra EXACTLY pass when the view is// already measured as the correct size. In API 23 and below, this// extra pass is required to make LinearLayout re-distribute weight.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);//强制重新布局或需要重新布局if (forceLayout || needsLayout) {// first clears the measured dimension flagmPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;//尝试解析RTL相关的属性resolveRtlPropertiesIfNeeded();//没有forceLayout时,尝试从缓存获取int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);//获取缓存失败,或忽略缓存时,才开始测量if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag backonMeasure(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 neededsetMeasuredDimensionRaw((int) (value >> 32), (int) value);mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}...........}mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;//存入缓存mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
  • 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

从上面的代码来看,当需要(包括强制)重新布局且不使用(包括无缓存)缓存数据时, 
才会调用onMeasure进行View的测量工作。

上述代码的整体流程,大致如下图所示: 

2.3 ViewGroup的onMeasure 
onMeasure函数一般会被View的子类覆盖,因此对于DecorView而言, 
实际调用的应该是FrameLayout的onMeasure方法。

我们来跟进一下FrameLayout的onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//FrameLayout是ViewGroup的子类, 此处获取子View的数量int count = getChildCount();//长或宽的SpecMode不为EXACTLY时, measureMatchParentChildren置为true//意味着ViewGroup的长或宽为wrap_contentfinal boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;mMatchParentChildren.clear();int maxHeight = 0;int maxWidth = 0;int childState = 0;//依次measure子Viewfor (int i = 0; i < count; i++) {final View child = getChildAt(i);//判断能否measure该子Viewif (mMeasureAllChildren || child.getVisibility() != GONE) {//具体的测量函数measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//不断迭代出子View需要的最大宽度和最大高度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);//迭代childStatechildState = combineMeasuredStates(childState, child.getMeasuredState());if (measureMatchParentChildren) {if (lp.width == LayoutParams.MATCH_PARENT ||lp.height == LayoutParams.MATCH_PARENT) {//统计matchParentChildrenmMatchParentChildren.add(child);}}}}// Account for padding too// 最大宽度和高度需要叠加paddingmaxWidth += 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());}//经过了以上一系列步骤后,我们就得到了ViewGroup的maxHeight和maxWidth的最终值//表示当前容器用这个尺寸就能够正常显示其所有子View//此处resolveSizeAndState根据数值、MeasureSpec和childState计算出最终的数值//然后用setMeasuredDimension保存到mMeasuredWidth与mMeasuredHeight成员变量 (定义于View.java)setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));//部分子View需要做最后的测量//当ViewGroup存在wrap_content的size(初始时,未明确定义大小)//且child View存在match_parent的size时(需要依赖父容器)//那么父容器计算完毕后,这类child view需要重新测量count = mMatchParentChildren.size();if (count > 1) {for (int i = 0; i < count; i++) {//根据父容器的参数生成新的约束条件............//重新测量child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}}
}
  • 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

从以上代码的执行流程,我们可以看到,作为容器的ViewGroup, 
将通过measureChildWithMargins方法,对所有子View进行测量, 
然后才会计算自身的测量结果。

FrameLayout的onMeasure函数整体流程可以概括为下图: 

2.4 measureChildWithMargins 
接下来,我们来看下ViewGroup的measureChildWithMargins方法:

protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {//获取子View的LayoutParamsfinal 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);//调用子View的measurechild.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

从上述代码可以看出,ViewGroup会利用getChildMeasureSpec函数计算出子View的约束条件, 
然后再调用子View的measure函数。

我们看看getChildMeasureSpec函数:

// 从measureChildWithMargins函数,可以看出:
// spec为父View的MeasureSpec
// padding为父View在相应方向的已用尺寸, 加上父View的padding和子View的margin
// childDimension为子View的LayoutParams相应方向的值
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);//得到父View在相应方向上的可用大小int size = Math.max(0, specSize - padding);//保存最终结果int resultSize = 0;int resultMode = 0;switch (specMode) {// Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:// 表示子View的LayoutParams指定了具体大小值(xx dp)if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size. So be it.// 子View为match_parentresultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.// 子View为wrap_contentresultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = 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;case MeasureSpec.UNSPECIFIED://不关注................}//noinspection ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
  • 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的MeasureSpec和子View的LayoutParams生成子View的MeasureSpec的过程, 
从代码可以看出: 
当子View指定了具体的大小时,resultSize就是指定的size,resultMode为EXACTLY, 
父View对其没有影响; 
当子View指定为MATCH_PARENT时,resultSize为父View可用的size, 
resultMode与父View一致; 
当子View指定为WRAP_CONTENT时,resultSize为父View可用的size, 
resultMode为AT_MOST。

从前文我们知道,获取完子View的MeasureSpec后, 
measureChildWithMargins就会调用子View的measure方法。 
对于ViewGroup及其子类而言,将重新递归调用ViewGroup的onMeasure方法; 
对于View及其子类而言,将调用View的onMeasure方法。

由于measureChildWithMargins会递归调用ViewGroup的onMeasure方法, 
可以看出整个View的测量顺序是先序遍历的,但最终计算结果时是后序遍历的, 
即子View测量完毕后,才能得到父View的size。

2.5 View的onMeasure 
现在我们跟进一下View的onMeasure方法:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
  • 1
  • 2
  • 3
  • 4

从代码可以看出,普通View只需要完成自身的测量工作即可。 
View以getDefaultSize方法的返回值来作为测量结果,通过setMeasuredDimension方法进行设置。

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;// AT_MOST和EXACTLY这两种情况都返回了SpecSize作为result// 自定义View直接继承View类时,需要自己实现// 否则wrap_content就和match_parent效果一样case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

三、总结 
至此,我们大致了解了View的测量流程。 
个人觉得重点在于了解MeasureSpec对测量过程的影响, 
同时知道测量的顺序是先序遍历,计算最终结果是后序遍历即可。

此外,当父容器的宽或高为wrap_content,其子View的宽或高为match_parent时, 
父容器得到最终的宽、高后,需要重新测量这部分子View。

下一篇博客,我们将继续关注View绘制的布局流程。

版权声明:转载请注明:http://blog.csdn.net/gaugamela/article

Android O: View的绘制流程(二):测量相关推荐

  1. Android O: View的绘制流程(三):布局和绘制

    前一篇文章Android O: View的绘制流程(二):测量中,  我们分析了View的测量流程.  当View测量完毕后,就要开始进行布局和绘制相关的工作,  本篇文章就来分析下这部分流程. 一. ...

  2. Android之View的绘制流程解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 自定义View在Android中占据着非常重要的地位,因此了解View的 ...

  3. Android O: View的绘制流程(一): 创建和加载

    从这篇博客开始,我们会用几篇文章,  基于Android O的代码,分析一下View的绘制流程. 在分析具体的绘制流程前,我们先来了解一下XML中定义的View,  如何被创建和加载. 一.setCo ...

  4. 从源码解析-Android中View的绘制流程及performTraversals方法

    谈谈Activity的setContentView是怎么加载XML视图的 谈谈Activity的View怎么与View绘制工具ViewRootImpl关联的 在前面两篇文章中分析了View是如何跟绘制 ...

  5. Android View的绘制流程(1) -- 测量onMeasure

    鉴于是首篇讲解自定义view流程,之前也在网上搜了一些博主的博客看了看,都是大同小异,今天抽时间自己总结一下,分享一下自己的感悟,也算是一篇笔记. (本篇为开头篇,稍微讲述一下有关的东西) View的 ...

  6. 【Android 应用开发】UI绘制流程 ( 生命周期机制 | 布局加载机制 | UI 绘制流程 | 布局测量 | 布局摆放 | 组件绘制 | 瀑布流布局案例 )

    文章目录 一. 博客相关资料 及 下载地址 1. 代码查看方法 ( ① 直接获取代码 | ② JAR 包替换 ) 2. 本博客涉及到的源码查看说明 二. Activity 生命周期回调机制 1. An ...

  7. Android自定义View系列之详解View的绘制流程

    目录 一.开场白 二.View的绘制流程 2.1测量的过程 2.2布局的过程 2.3绘制的过程 一.开场白 开讲之前我们先预设一种自定义ViewGroup的场景:我们知道LinearLayout.Fr ...

  8. android自定义view流程,Android 自定义View--从源码理解View的绘制流程

    前言 在Android的世界里,View扮演着很重要的角色,它是Android世界在视觉上的具体呈现.Android系统本身也提供了很多种原生控件供我们使用,然而在日常的开发中我们很多时候需要去实现一 ...

  9. 【Android面试】View的绘制流程

    目录 View的绘制流程简介 Activity和window和view 的关系 Activity和Window是什么时候建立联系的呢? ViewRootImpl View的绘制流程总结 View的绘制 ...

最新文章

  1. JS中的this对象详解
  2. nodejs之http-proxy几点常见问题
  3. IM开发基础知识补课(一):正确理解前置HTTP SSO单点登陆接口的原理
  4. java访问mongodb_Java中获取MongoDB连接的方法详解
  5. There is no Action mapped for namespace / and action name accredit.
  6. Linux系统(七)组管理和用户管理
  7. 2018 【第九届蓝桥杯省赛】 C/C++ B组
  8. SPSS 分层回归(图文+数据集)【SPSS 027期】
  9. IOS日历控件JTCalendar
  10. bin文件的安装方法
  11. MD5算法如何被破解
  12. 记录第二次进行的助教培训-评分
  13. 如何用python爬取股票数据选股_用python爬取股票数据
  14. [SDOI2015] 星际战争
  15. “会说话的汤姆猫家族-时代逐光者”3D数字藏品中奖名单公布
  16. dp主机_DP接口与HDMI接口的区别?
  17. 系统学习深度学习(六) --LSTM总结
  18. 从谷歌创始人的公开信中所学到的
  19. 计算机 90学时培训总结,90学时培训心得总结
  20. 统计一个数中二进制数1的个数(新手篇10)

热门文章

  1. java 获取当前时间月加1 ,年加1
  2. 正确理解HTML,XHTML页面的头部doctype定义
  3. 使用Oracle数据库开发中的一个技巧
  4. Win10+tensorflow:SSD调试问题:Unable to open table file ../checkpoints/ssd_300_vgg.ckpt
  5. Python学习笔记:电子邮件,POP3收取邮件
  6. 嵌入式驱动开发应具备的三大基础
  7. 图解linux下top命令的使用
  8. 5.14 图像频域处理
  9. 海南师范大学计算机设计大赛证书,我校品牌VI设计作品在中国大学生计算机设计大赛海南省赛中获得一等奖...
  10. oracle恢复某个表的数据库,如何从rman的全备份中恢复一张表?