前言

本文从measure流程开始: 使用performTraversals方法,该方法内部会分别调用

MeasureSpec

MeasureSpec是View类的一个内部类,我们先看看官方文档对MeasureSpec类的描述:

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.

意思其实是 MeasureSpec封装了父布局对子视图的布局要求,由尺寸和模式组成,每个MeasureSpec表示其对子View的宽度或高度要求

其内部原理是一个32位的int值,高2位表示SpecMode,低30位表示SpecSize; 可以通过makeMeasureSpec来封装一个MeasureSpec,通过getSize()和getMode()来解封获取MeasureSpec内包含的尺寸大小和模式信息。解封操作是通过位运算来获取高2位(getMode())或者低30位(getSize())

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n203" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#class in View&MeasureSpec public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHifT; /** * UNSPECIFIED 模式: * 父View不对子View有任何限制,子View需要多大就多大 */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * EXACTYLY 模式: * 父View已经测量出子Viwe所需要的精确大小,这时候View的最终大小 * 就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式 */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * AT_MOST 模式: * 子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值, * 即对应wrap_content这种模式 */ public static final int AT_MOST = 2 << MODE_SHIFT; //将size和mode打包成一个32位的int型数值 //高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小 public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } //将32位的MeasureSpec解包,返回SpecMode,测量模式 public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } //将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小 public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } //... } </pre>

我们在使用View时是直接设置LayoutParams; 但是在View测量的时候,系统会将该LayoutParams在结合父布局所给它的MeasureSpec来确定View的测量后的宽高然后生成自己的MeasureSpec,该MeasureSpec保存了View自身的宽高信息同时也包含了对子View的限制规则。

上述会有一个疑问, 既然View的测量要根据自身的LayoutParams和父布局的MeasureSpec,那么顶层View(DecorView)哪里来的父布局MeasureSpec?

答: 其实DecorView的测量和普通View是有区别的,DecorView的MeasureSpec是由屏幕尺寸和LayoutParams来决定的,并且DecorView的默认LayoutParams是match_parent(在初始化DecorView时可知),而普通View的MeasureSpec是由其LayoutParams和父布局的MeasureSpec决定的。

performTraversals开始测量

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n208" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#class in ViewrootImpl private void performTraversals() { ... if (!mStopped) { ... // 根据屏幕尺寸大小和lp(match_parent)来生成DecorView的MeasureSpec int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 1 } if (didLayout) { performLayout(lp, desiredWindowWidth, desiredWindowHeight); // 2 ... } if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); // 3 } } ... } 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; } </pre>

上面说明DecorView的MeasureSpec生成规则是:

LayoutParams

MeasureSpec

MATCH_PARENT

MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);

WRAP_CONTENT

MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);

else(固定尺寸)

MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);

生成之后将结果设置给childHeightMeasureSpec开始执行performMeasure进行测量:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n224" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#class in ViewRootImpl private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { // DecorView开始测量,会走到FrameLayout#measure() -> View#measure() mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } </pre>

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n225" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#class in View public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { ... 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); ... } else { ... // 有缓存直接设置缓存结果 setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } ... } ... } </pre>

我们从最顶层View(DecorView)为例进行测量分析,具体继续往里测量子View是一个递归过程; DecorView的真正测量逻辑在FrameLayout#onMeasure(widthMeasureSpec, heightMeasureSpec); 下面代码分析都以FrameLayout为例

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n227" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#class in FrameLayout @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); // 记录FrameLayout布局参数是否是wrap_content final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; // 遍历测量每一个非GONE的子View for (int i = 0; i < count; i ) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { // 将FrameLayout对子View的约束条件(MeasureSpec)传入开始测量子View,这里一会儿展开 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); // 对每一个子View都与本地记录的最大宽高(maxWidth,maxHeight)进行比较,计算出所有子View中需要的最大的宽度和高度。因为假若FrameLayout的LayoutParams属性是wrap_content的话,那FrameLayout的大小取决于子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); childState = combineMeasuredStates(childState, child.getMeasuredState()); // 假设FrameLayout的LayoutParams是wrap_content且子View的LayoutParams的宽高中任何一个属性是match_parent的话,就记录下来一会儿会进行重新测量,因为这时子View的大小受FrameLayout的最终大小影响 if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } ...省略前景、背景参与最大宽高计算的代码,这部分很简单,就是当前max与前景、背景取最大值 // 保存测量结果,这里一会儿展开 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); // 注意,这里待会儿分析子View测量的时候会提及到此,先记录一下当个锚点后续好回头看 // ***************************************************************** count = mMatchParentChildren.size(); if (count > 1) { // 当FrameLayout LayoutParams为wrap_content时,对所有LayoutParams为match_parent的子View重新生成一个对子View的布局要求(MeasureSpec)然后进行子View的重新测量 for (int i = 0; i < count; i ) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); /** * 如果子View的宽度是match_parent属性,那么对当前FrameLayout的MeasureSpec修改: * 把widthMeasureSpec的宽度规格修改为:总宽度 - padding - margin,这样做的意思是: * 对于子Viw来说,如果要match_parent,那么它可以覆盖的范围是FrameLayout的测量宽度 * 减去padding和margin后剩下的空间,父View剩多少空间就给你多少空间。 * * 以下两点的结论,可以查看getChildMeasureSpec()方法: * * 如果子View的宽度是一个确定的值,比如50dp,那么FrameLayout的widthMeasureSpec的宽度规格修改为: * SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式 * * 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格修改为: * SpecSize为FrameLayout的宽度减去padding减去margin,SpecMode为AT_MOST模式 */ final int childWidthMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { 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); } ...高度同理 // 定好之后进行子View的重新测量,这时重走测量流程,如果子View内又包含子View,则会一层一层往里测量 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } } </pre>

通过上述代码以及注释可以理解,在onMeasure中总共做了这几件事:

1.遍历子View,调用measureChildWithMargins方法对子View进行测量(具体测量规则稍后再说,此处暂时直接认为已经测量完毕) 2.FrameLayout布局参数为wrap_content时,记录下所有宽或者高布局参数为match_parent的子View到mMatchParentChildren中以便后续进行重新测量 3.保存下测量结果,至此FrameLayout的宽高已经测量完毕,宽高已经确定 4.取出mMatchParentChildren中的所有View对其重新设置MeasureSpec然后执行测量流程,重设MeasureSpec的规则是(以width为例):

子View LayoutParams

MeasureSpec计算规则

match_parent

MeasureSpec.makeMeasureSpec(FrameLayout宽度 - padding - margin,MeasureSpec.EXACTLY)

match_parent

MeasureSpec.makeMeasureSpec(FrameLayout宽度 - padding - margin, MeasureSpec.AT_MOST)

固定值(如50dp)

MeasureSpec.makeMeasureSpec(50dp, MeasureSpec.EXACTLY)

5.使用新生成的MeasureSpec对子View进行重新测量,又是一个遍历的过程

整个流程通了之后,我们来看一下子View具体的测量规则:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n245" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#class in ViewGroup 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); // 子View根据测算出来的MeasureSpec已经初步确定了自己的大小,此时如果它也有子View会继续往里进行测量,知道最深层次的View测量完为止 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) { // 子的宽度为确定值,那么确定子的宽度为childDimension,模式为EXACTLY resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. // 子View宽度为match_parent,此时父是一个精确模式,所以这时子的宽度也可以确定,子宽度为父剩下的可用宽度,模式为EXACTLY 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. // 子宽度为wrap_content,此时表示子想自己控制宽度,但是父是精确的必须限制子的宽度不许超过父的剩余可用宽度,此时子宽度暂时设置为父的剩余宽度,模式为AT_MOST尽可能大 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us // 父宽度不确定 case MeasureSpec.AT_MOST: // 子的宽度为确定值,那么确定子的宽度为childDimension,模式为EXACTLY 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. /** * 子想撑满父,但是此时父的宽度并不确定,这是只能先把父剩余的宽度都给它, * 然后把模式设置为AT_MOST,说明它也是个不确定的值, * 一直等到后续子都测量完了才能重新来确定自身宽度, * 真正重新测量的逻辑就是走到FrameLayout咱们之前分析的FrameLayout#onMeasure()星号注释处开始, * 父会重新设置其对子的MeasureSpec然后重新对子进行测量,这时候子View就能得到准确宽度了 */ 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. // 当子宽度也是不确定的时候,直接将父的最大可用宽度给它,并且设置其模式为AT_MOST,也会等到后续子都确定了宽度之后进行重新测量 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: ... 这种模式为系统使用的,我们一般用不到,不做分析 break; } // 一系列规则完成之后生成MeasureSpec,此时子View的大小测量暂时告一段落了,返回结果 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } </pre>

逻辑上感觉很绕,其实梳理之后很好理解,子View的大小根据其自身的LayoutParams和父给它的MeasureSpec来共同决定,大体规则就是遵循:

1.父大小精确: a.子LayoutParams大小设置准确值,结果就是:MeasureSpec(准确值,EXACTLY) b.子想撑满父,则把父剩余的空间全给它,结果就是:MeasureSpec(父SpecSize,EXACTLY) c.子也不确定它想自己控制大小,则父只能把最大剩余空间给它,只要它不超出就行,结果就是:MeasureSpec(父SpecSize,AT_MOST)

2.父大小不确定: a.子LayoutParams大小设置准确值,结果就是:MeasureSpec(准确值,EXACTLY) b.子想撑满父,父把能给它的都给它,结果就是:MeasureSpec(父SpecSize,AT_MOST),后面会再测量一次 c.子想控制自己大小,父也是把能给它的都给它保证它不能超过父剩余的大小,结果就是:MeasureSpec(父SpecSize,AT_MOST)

父大小确定

子LayoutParams大小设置准确值:MeasureSpec(准确值,EXACTLY)

父大小确定

子想撑满父,则把父剩余的空间全给它:MeasureSpec(父SpecSize,EXACTLY))

父大小确定

子想自己控制大小,则父只能把最大剩余大小给它要求子不能超过这个大小:MeasureSpec(父SpecSize,AT_MOST)

父大小不确定

子LayoutParams大小设置准确值:MeasureSpec(准确值,EXACTLY)

父大小不确定

子想撑满父,则把父剩余的空间全给它:MeasureSpec(父SpecSize,AT_MOST),后续会重新测量确定真正的大小

父大小不确定

子想自己控制大小,则父只能把最大剩余大小给它要求子不能超过这个大小:MeasureSpec(父SpecSize,AT_MOST),也会重新测量确定真正大小

参考网上的一张图片,过程就是:

android中view的绘制(一文读懂AndroidUI绘制中的)(1)

接着看下测量完成之后需要将测量的结果设置给mMeasuredWidth和mMeasuredHeight,后续我们使用的时候调用View.getMeasureWidth()等方法时才能拿到具体的值; 每个View在测量完成之后是通过 setMeasuredDimension(int measuredWidth, int measuredHeight)方法来将结果保存给View的。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n271" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#class in View protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { ... setMeasuredDimensionRaw(measuredWidth, measuredHeight); } private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; } </pre>

至此测量流程已经结束

真正当我们自定义View时如果需要涉及到View的测量流程,在测量的最后必须将结果通过setMeasuredDimensionRaw设置给View,这样才是一个完整的自定义View的测量流程,否则你所测量出来的结果是不生效的

大致可以这么做:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n274" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//假设自定义一个View宽度为10,高度为20 public class CustomView extends View { public CustomView(Context context) { super(context); } // 方法1 直接继承onMeasure,然后生成新的MeasureSpec传给父 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int newWidthSpec = MeasureSpec.makeMeasureSpec(10, MeasureSpec.AT_MOST); int newHeightSpec = MeasureSpec.makeMeasureSpec(20, MeasureSpec.AT_MOST); super.onMeasure(newWidthSpec, newHeightSpec); } // 方法2 确定好宽高之后直接设置给View @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(10, 20); } } </pre>

好了,文章基本上就到这里,measure详细流程的分析就到此完毕了!,如有地方不对或者有不同理解的可以提出来

有需要文中完整代码的同学 可以 私信发送:“底层源码” 即可 免费获取

现在点击还可以获得 更多《Android 学习笔记+源码解析+面试视频》

最后我想说:

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

,