概述
上一篇 AndroID VIEw 绘制流程之 DecorVIEw 与 VIEwRootImpl 分析了在调用 setContentVIEw 之后,DecorVIEw 是如何与 activity 关联在一起的,最后讲到了 VIEwRootImpl 开始绘制的逻辑。本文接着上篇,继续往下讲,开始分析 vIEw 的绘制流程。
上文说到了调用 performTraversals 进行绘制,由于 performTraversals 方法比较长,看一个简化版:
// VIEwRootImpl 类private voID performTraversals() { 这个方法代码非常多,但是重点就是执行这三个方法 执行测量 performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec); 执行布局(VIEwGroup)中才会有 performlayout(lp,mWIDth,mHeight); 执行绘制 performDraw();}
其流程具体如下:
VIEw的整个绘制流程可以分为以下三个阶段:
measure: 判断是否需要重新计算 VIEw 的大小,需要的话则计算;
layout: 判断是否需要重新计算 VIEw 的位置,需要的话则计算;
draw: 判断是否需要重新绘制 VIEw,需要的话则重绘制。
@H_403_103@MeasureSpec在介绍绘制前,先了解下 MeasureSpec。MeasureSpec 封装了父布局传递给子布局的布局要求,它通过一个 32 位 int 类型的值来表示,该值包含了两种信息,高两位表示的是 SpecMode
(测量模式),低 30 位表示的是 Specsize
(测量的具体大小)。下面通过注释的方式来分析来类:
/** * 三种SpecMode: * 1.UnspecIFIED * 父 VIEwGroup 没有对子VIEw施加任何约束,子 vIEw 可以是任意大小。这种情况比较少见,主要用于系统内部多次measure的情形,
* 用到的一般都是可以滚动的容器中的子vIEw,比如ListVIEw、GrIDVIEw、RecyclerVIEw中某些情况下的子vIEw就是这种模式。
* 一般来说,我们不需要关注此模式。 * 2.EXACTLY * 该 vIEw 必须使用父 VIEwGroup 给其指定的尺寸。对应 match_parent 或者具体数值(比如30dp) * 3.AT_MOST * 该 VIEw 最大可以取父VIEwGroup给其指定的尺寸。对应wrap_content * * MeasureSpec使用了二进制去减少对象的分配。 */ public class MeasureSpec { 进位大小为2的30次方(int的大小为32位,所以进位30位就是要使用int的最高位和第二高位也就是32和31位做标志位) private static final int MODE_SHIFT = 30; 运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000(11后跟30个0) (遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0) int MODE_MASK = 0x3 << MODE_SHIFT; 0向左进位30,就是00 00000000000(00后跟30个0) int UnspecIFIED = 0 << MODE_SHIFT; 1向左进位30,就是01 00000000000(01后跟30个0) int EXACTLY = 1 << 2向左进位30,就是10 00000000000(10后跟30个0) int AT_MOST = 2 << * 根据提供的size和mode得到一个详细的测量结果 第一个return: measureSpec = size + mode; (注意:二进制的加法,不是十进制的加法!) 这里设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100 第二个return: size &; ~MODE_MASK就是取size 的后30位,mode & MODE_MASK就是取mode的前两位,最后执行或运算,得出来的数字,前面2位包含代表mode,后面30位代表size int makeMeasureSpec(int size, int mode) { if (sUsebrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } * 获得SpecMode mode = measureSpec & MODE_MASK; MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值 int getMode( measureSpec) { return (measureSpec & MODE_MASK); } * 获得Specsize size = measureSpec & ~MODE_MASK; 原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size int getSize(return (measureSpec & ~MODE_MASK); } }
顺便提下 MATCH_PARENT 和 WRAP_CONTENT 这两个代表的值,分别是 -1 和 -2。
* Special value for the height or wIDth requested by a VIEw. * MATCH_PARENT means that the vIEw wants to be as big as its parent,* minus the parent's padding,if any. Introduced in API Level 8. */ public static final int MATCH_PARENT = -1; * Special value for the height or wIDth requested by a VIEw. * WRAP_CONTENT means that the vIEw wants to be just large enough to fit * its own internal content,taking its own padding into account. int WRAP_CONTENT = -2;@H_403_103@DecorvIEw 尺寸的确定
在 performTraversals 中,首先是要确定 DecorVIEw 的尺寸。只有当 DecorVIEw 尺寸确定了,其子 VIEw 才可以知道自己能有多大。具体是如何去确定的,可以看下面的代码:
Activity窗口的宽度和高度 desireDWindowWIDth; desireDWindowHeight;...用来保存窗口宽度和高度,来自于全局变量mWinFrame,这个mWinFrame保存了窗口最新尺寸Rect frame = mWinFrame;构造方法里mFirst赋值为true,意思是第一次执行遍历吗 (mFirst) { 是否需要重绘 mFullRedrawNeeded = true; 是否需要重新确定Layout mLayoutRequested = ; // 这里又包含两种情况:是否包括状态栏 判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的DecorvIEw的高度和宽度 (shouldUsedisplaySize(lp)) { NOTE -- system code,won't try to do compat mode. Point size = new Point(); mdisplay.getRealSize(size); desireDWindowWIDth = size.x; desireDWindowHeight = size.y; } { 宽度和高度为整个屏幕的值 Configuration config = mContext.getResources().getConfiguration(); desireDWindowWIDth = diptopx(config.screenWIDthDp); desireDWindowHeight = diptopx(config.screenHeightDp); } ... else{ // 这是window的长和宽改变了的情况,需要对改变的进行数据记录 如果不是第一次进来这个方法,它的当前宽度和高度就从之前的mWinFrame获取 desireDWindowWIDth = frame.wIDth(); desireDWindowHeight = frame.height(); * mWIDth和mHeight是由WindowManagerService服务计算出的窗口大小, * 如果这次测量的窗口大小与这两个值不同,说明WMS单方面改变了窗口的尺寸 if (desireDWindowWIDth != mWIDth || desireDWindowHeight != mHeight) { if (DEBUG_ORIENTATION) Log.v(mTag,"VIEw " + host + " resized to: " + frame); 需要进行完整的重绘以适应新的窗口尺寸 mFullRedrawNeeded = ; 需要对控件树进行重新布局 mLayoutRequested = window窗口大小改变 windowsizeMayChange = ; } } ... // 进行预测量 (layoutRequested){ ... (mFirst) { 视图窗口当前是否处于触摸模式。 mAttachInfo.mIntouchMode = !mAddedtouchMode; 确保这个Window的触摸模式已经被设置 ensuretouchModeLocally(mAddedtouchMode); } { 六个if语句,判断insects值和上一次比有什么变化,不同的话就改变insetsChanged insects值包括了一些屏幕需要预留的区域、记录一些被遮挡的区域等信息 if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) { insetsChanged = ; } ... // 这里有一种情况,我们在写dialog时,会手动添加布局,当设定宽高为Wrap_content时,会把屏幕的宽高进行赋值,给出尽量长的宽度 * 如果当前窗口的根布局的wIDth或height被指定为 WRAP_CONTENT 时, * 比如Dialog,那我们还是给它尽量大的长宽,这里是将屏幕长宽赋值给它 */ if (lp.wIDth == VIEwGroup.LayoutParams.WRAP_CONTENT || lp.height == VIEwGroup.LayoutParams.WRAP_CONTENT) { windowsizeMayChange = ; 判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的DecorvIEw的高度和宽度 (shouldUsedisplaySize(lp)) { Point(); mdisplay.getRealSize(size); desireDWindowWIDth = size.x; desireDWindowHeight = size.y; } { Configuration config = res.getConfiguration(); desireDWindowWIDth = diptopx(config.screenWIDthDp); desireDWindowHeight = diptopx(config.screenHeightDp); } } } }}
这里主要是分两步走:
如果是第一次测量,那么根据是否有状态栏,来确定是直接使用屏幕的高度,还是真正的显示区高度。
如果不是第一次,那么从 mWinFrame 获取,并和之前保存的长宽高进行比较,不相等的话就需要重新测量确定高度。
当确定了 DecorVIEw 的具体尺寸之后,然后就会调用 measureHIErarchy 来确定其 MeasureSpec :
Ask host how big it wants to be windowsizeMayChange |= measureHIErarchy(host,lp,res,desireDWindowWIDth,desireDWindowHeight);
其中 host 就是 DecorVIEw,lp 是 wm 在添加时候传给 DecorVIEw 的,最后两个就是刚刚确定显示宽高 ,看下方法的具体逻辑 :
boolean measureHIErarchy(final VIEw host,final WindowManager.LayoutParams lp,final Resources res,1)">int desireDWindowWIDth,1)"> desireDWindowHeight) { chilDWIDthMeasureSpec; childHeightmeasureSpec; boolean windowsizeMayChange = false;boolean goodMeasure = ;
// 说明是 dialog 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. final displayMetrics packageMetrics = res.getdisplayMetrics(); res.getValue(com.androID.internal.R.dimen.config_prefDialogWIDth,mTmpValue,1)">); int baseSize = 0 // 获取一个基本的尺寸 if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { baseSize = ()mTmpValue.getDimension(packageMetrics); } if (DEBUG_DIALOG) Log.v(mTag,"Window " + mVIEw + ": baseSize=" + baseSize + ",desireDWindowWIDth=" + desireDWindowWIDth);
// 如果大于基本尺寸 if (baseSize != 0 && desireDWindowWIDth > baseSize) { chilDWIDthMeasureSpec = getRootMeasureSpec(baseSize,lp.wIDth); childHeightmeasureSpec = getRootMeasureSpec(desireDWindowHeight,lp.height); performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec); host.getMeasuredHeight() + ") from wIDth spec: " + MeasureSpec.toString(chilDWIDthMeasureSpec) + " and height spec: " + MeasureSpec.toString(childHeightmeasureSpec));
// 判断测量是否准确 if ((host.getMeasureDWIDthAndState()&VIEw.MEASURED_STATE_TOO_SMALL) == 0) { goodMeasure = ; } { DIDn't fit in that size... try expanding a bit. baseSize = (baseSize+desireDWindowWIDth)/2; performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec); ); ) { ); goodMeasure = ; } } } } // 这里就是一般 DecorVIEw 会走的逻辑 goodMeasure) { chilDWIDthMeasureSpec = getRootMeasureSpec(desireDWindowWIDth,lp.wIDth); childHeightmeasureSpec = performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec);
// 与之前的尺寸进行对比,看看是否相等,不想等,说明尺寸可能发生了变化 if (mWIDth != host.getMeasureDWIDth() || mHeight != host.getMeasuredHeight()) { windowsizeMayChange = ; } } return windowsizeMayChange; }
上面主要主要做的就是来确定父 VIEw 的 MeasureSpec。但是分了两种不同类型:
如果宽是 WRAP_CONTENT 类型,说明这是 dialog,会有一些针对 dialog 的处理,最终会调用 performMeasure 进行测量;
对于一般 Activity 的尺寸,会调用 getRootMeasureSpec MeasureSpec 。
下面看下 DecorVIEw MeasureSpec 的计算方法:
int getRootMeasureSpec(int windowsize,1)"> rootDimension) { 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; VIEwGroup.LayoutParams.WRAP_CONTENT: Window can resize. Set max size for root vIEw. measureSpec =default: Window wants to be an exact size. Force root vIEw to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,1)">; } return measureSpec; }
该方法主要是根据 VIEw 的 MeasureSpec 是根据宽高的参数来划分的。
MATCH_PARENT :精确模式,大小就是窗口的大小;
WRAP_CONTENT :最大模式,大小不定,但是不能超过窗口的大小;
固定大小:精确模式,大小就是指定的具体宽高,比如100dp。
对于 DecorVIEw 来说就是走第一个 case,到这里 DecorVIEw 的 MeasureSpec 就确定了,从 MeasureSpec 可以得出 DecorVIEw 的宽高的约束信息。
当父 VIEwGroup 对子 VIEw 进行测量时,会调用 VIEw 类的 measure
方法,这是一个 final 方法,无法被重写。VIEwGroup 会传入自己的 wIDthMeasureSpec
和 heightmeasureSpec
,分别表示父 VIEw 对子 VIEw 的宽度和高度的一些限制条件。尤其是当 VIEwGroup 是 WRAP_CONTENT 的时候,需要优先测量子 VIEw,只有子 VIEw 宽高确定,VIEwGroup 才能确定自己到底需要多大的宽高。
当 DecorVIEw 的 MeasureSpec 确定以后,VIEwRootImpl 内部会调用 performMeasure 方法:
voID performMeasure(int chilDWIDthMeasureSpec,1)"> childHeightmeasureSpec) { if (mVIEw == null) { ; } Trace.traceBegin(Trace.TRACE_TAG_VIEW,"measure"); try { mVIEw.measure(chilDWIDthMeasureSpec,childHeightmeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
该方法传入的是对 DecorVIEw 的 MeasureSpec,其中 mVIEw 就是 DecorVIEw 的实例,接下来看 measure() 的具体逻辑:
* 调用这个方法来算出一个VIEw应该为多大。参数为父VIEw对其宽高的约束信息。 * 实际的测量工作在onMeasure()方法中进行 */voID measure(int wIDthMeasureSpec,1)"> heightmeasureSpec) { ......
// Suppress sign extension for the low bytes long key = (long) wIDthMeasureSpec << 32 | (long) heightmeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
若mPrivateFlags中包含PFLAG_FORCE_LAYOUT标记,则强制重新布局 比如调用VIEw.requestLayout()会在mPrivateFlags中加入此标记 boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; boolean specChanged = wIDthMeasureSpec != molDWIDthMeasureSpec || heightmeasureSpec != moldHeightmeasureSpec; boolean isspecExactly = MeasureSpec.getMode(wIDthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightmeasureSpec) == MeasureSpec.EXACTLY; boolean matchesSpecsize = getMeasureDWIDth() == MeasureSpec.getSize(wIDthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightmeasureSpec); boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isspecExactly || !matchesSpecsize); 需要重新布局 if (forceLayout || needsLayout) {
// first clears the measured dimension flag 标记为未测量状态
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
// 对阿拉伯语、希伯来语等从右到左书写、布局的语言进行特殊处理
resolveRtlPropertIEsIfNeeded();
先尝试从缓从中获取,若forceLayout为true或是缓存中不存在或是 忽略缓存,则调用onMeasure()重新进行测量工作 int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { measure ourselves,this should set the measured dimension flag back onMeasure(wIDthMeasureSpec,heightmeasureSpec); . . . } { 缓存命中,直接从缓存中取值即可,不必再测量 long value = mMeasureCache.valueAt(cacheIndex); Casting a long to int drops the high 32 bits,no mask needed setMeasuredDimensionRaw((int) (value >> 32),() value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 如果自定义的VIEw重写了onMeasure方法,但是没有调用setMeasuredDimension()方法就会在这里抛出错误;
// flag not set,setMeasuredDimension() was not invoked,we raise//到了这里,VIEw已经测量完了并且将测量的结果保存在VIEw的mMeasureDWIDth和mMeasuredHeight中,将标志位置为可以layout的状态
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("VIEw with ID " + getID() + ": "
+ getClass().getname() + "#onMeasure() dID not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_required;
} molDWIDthMeasureSpec = wIDthMeasureSpec; moldHeightmeasureSpec = heightmeasureSpec;
// 保存到缓存中 mMeasureCache.put(key,((long) mMeasureDWIDth) << 32 | (long) mMeasuredHeight & 0xffffffffL); suppress sign extension}
这里要注意的是,这是一个 final 方法,不能被继承。这个方法只在 VIEw 类里面。总结一下 measure()
都干了什么事:
调用 VIEw.measure()
方法时 VIEw 并不是立即就去测量,而是先判断一下要不要进行测量 *** 作,如果没必要,那么 VIEw 就不需要重新测量了,避免浪费时间资源
如果需要测量,在测量之前,会先判断是否存在缓存,存在直接从缓存中获取就可以了,再调用一下 setMeasuredDimensionRaw
方法,将从缓存中读到的测量结果保存到成员变量 mMeasureDWIDth
和 mMeasuredHeight
中。
如果不能从 mMeasureCache
中读到缓存过的测量结果,调用 onMeasure()
方法去完成实际的测量工作,并且将尺寸限制条件 wIDthMeasureSpec
和 heightmeasureSpec
传递给 onMeasure()
方法。关于 onMeasure()
方法,会在下面详细介绍。
将结果保存到 mMeasureDWIDth
和 mMeasuredHeight
这两个成员变量中,同时缓存到成员变量 mMeasureCache
中,以便下次执行 measure()
方法时能够从其中读取缓存值。
需要说明的是,VIEw 有一个成员变量 mPrivateFlags
,用以保存 VIEw 的各种状态位,在测量开始前,会将其设置为未测量状态,在测量完成后会将其设置为已测量状态。
DecorVIEw 是 FrameLayout 子类,这时候应该去看 FrameLayout 中的 onMeasure() 方法,代码具体如下:
protected voID onMeasure( heightmeasureSpec) {
// 获取子vIEw的个数 int count = getChildCount(); boolean measureMatchParentChildren = MeasureSpec.getMode(wIDthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightmeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0int maxWIDth = 0int childState = 0for (int i = 0; i < count; i++final VIEw child = getChildAt(i);
// mMeasureAllChildren 默认为FALSE,表示是否全部子 vIEw 都要测量,子vIEw不为GONE就要测量 if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 测量子vIEw measureChilDWithmargins(child,wIDthMeasureSpec,0,heightmeasureSpec,0);
// 获取子vIEw的布局参数 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 记录子vIEw的最大宽度和高度 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());
// 记录所有跟父布局有着相同宽或高的子vIEw (measureMatchParentChildren) { if (lp.wIDth == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } Account for padding too 子vIEw的最大宽高计算出来后,还要加上父VIEw自身的padding maxWIDth += getpaddingleftWithForeground() + getpaddingRightWithForeground(); maxHeight += getpaddingtopWithForeground() + getpaddingBottomWithForeground(); Check against our minimum height and wIDth maxHeight = Check against our foreground's minimum height and wIDth final Drawable drawable = getForeground(); if (drawable != ) { maxHeight = // 确定父 vIEw 的宽高 setMeasuredDimension(resolveSizeAndState(maxWIDth,childState),resolveSizeAndState(maxHeight,childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { mMatchParentChildren.get(i); final marginLayoutParams lp = (marginLayoutParams) child.getLayoutParams(); chilDWIDthMeasureSpec;
// 如果子vIEw的宽是MATCH_PARENT,那么宽度 = 父vIEw的宽 - 父padding - 子margin LayoutParams.MATCH_PARENT) { int wIDth = Math.max(0,getMeasureDWIDth() - getpaddingleftWithForeground() - getpaddingRightWithForeground() - lp.leftmargin - lp.rightmargin); chilDWIDthMeasureSpec = MeasureSpec.makeMeasureSpec( wIDth,MeasureSpec.EXACTLY); } { chilDWIDthMeasureSpec = getChildMeasureSpec(wIDthMeasureSpec,getpaddingleftWithForeground() + getpaddingRightWithForeground() + lp.leftmargin + lp.rightmargin,lp.wIDth); } childHeightmeasureSpec;
// 同理 if (lp.height ==int height = Math.max(0 getpaddingBottomWithForeground() - lp.topmargin - lp.bottommargin); childHeightmeasureSpec = MeasureSpec.makeMeasureSpec( height,1)"> { childHeightmeasureSpec = getChildMeasureSpec(heightmeasureSpec,getpaddingtopWithForeground() + getpaddingBottomWithForeground() + lp.topmargin + lp.bottommargin,lp.height); } child.measure(chilDWIDthMeasureSpec,childHeightmeasureSpec); } } }
FrameLayout 是 VIEwGroup 的子类,后者有一个 VIEw[] 类型的成员变量 mChildren,代表了其子 VIEw 集合。通过 getChildAt(i) 能获取指定索引处的子 VIEw,通过 getChildCount() 可以获得子 VIEw 的总数。
在上面的源码中,首先调用 measureChilDWithmargins() 方法对所有子 VIEw 进行了一遍测量,并计算出所有子 VIEw 的最大宽度和最大高度。而后将得到的最大高度和宽度加上padding,这里的 padding 包括了父 VIEw 的 padding 和前景区域的 padding。然后会检查是否设置了最小宽高,并与其比较,将两者中较大的设为最终的最大宽高。最后,若设置了前景图像,我们还要检查前景图像的最小宽高。
经过了以上一系列步骤后,我们就得到了 maxHeight 和 maxWIDth 的最终值,表示当前容器 VIEw 用这个尺寸就能够正常显示其所有子 VIEw(同时考虑了 padding 和 margin )。而后我们需要调用 resolveSizeAndState() 方法来结合传来的 MeasureSpec 来获取最终的测量宽高,并保存到 mMeasureDWIDth 与 mMeasuredHeight 成员变量中。
如果存在一些子 VIEw 的宽或高是 MATCH_PARENT,那么需要等父 VIEw 的尺寸计算出来后,重新计算这些子 VIEw 的 MeasureSpec,再来测量这些子 vIEw 的宽高。
这里提醒我们在自定义 VIEw 的时候需要考虑你的子 VIEw 是不是和你自定义的 VIEw 的大小是一样,如果一样,就需要等自定义 VIEw 的大小确定了,再重新测量一遍。
下面看看 measureChilDWithmargins() 方法具体逻辑:
* Ask one of the children of this vIEw to measure itself,taking into * account both the MeasureSpec requirements for this vIEw and its padding * and margins. The child must have marginLayoutParams The heavy lifting is * done in getChildMeasureSpec. * * @param child The child to measure * parentWIDthMeasureSpec The wIDth requirements for this vIEw * wIDthUsed Extra space that has been used up by the parent * horizontally (possibly by other children of the parent) * parentHeightmeasureSpec The height requirements for this vIEw * heightUsed Extra space that has been used up by the parent * vertically (possibly by other children of the parent) */ measureChilDWithmargins(VIEw child,1)">int parentWIDthMeasureSpec,1)"> wIDthUsed,1)">int parentHeightmeasureSpec,1)"> heightUsed) { (marginLayoutParams) child.getLayoutParams(); int chilDWIDthMeasureSpec = getChildMeasureSpec(parentWIDthMeasureSpec,mpaddingleft + mpaddingRight + lp.leftmargin + lp.rightmargin +int childHeightmeasureSpec = getChildMeasureSpec(parentHeightmeasureSpec,mpaddingtop + mpaddingBottom + lp.topmargin + lp.bottommargin + heightUsed,lp.height); child.measure(chilDWIDthMeasureSpec,childHeightmeasureSpec); }
该方法主要是获取子 VIEw 的 MeasureSpec,然后调用 child.measure() 来完成子 VIEw 的测量。下面看看子 VIEw 获取 MeasureSpec 的具体逻辑:
int getChildMeasureSpec(int spec,1)">int padding,1)"> childDimension) {
// 父 vIEw 的 mode 和 size int specMode = MeasureSpec.getMode(spec); int specsize = MeasureSpec.getSize(spec); // 去掉 padding int size = Math.max(0,specsize - padding); int resultSize = 0int resultMode = 0 (specMode) { Parent has imposed an exact size on us 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 = LayoutParams.WRAP_CONTENT) { Child wants to determine its own size. It can't be bigger than us. resultSize = MeasureSpec.AT_MOST; } Parent has imposed a maximum size on us MeasureSpec.AT_MOST: Child wants a specific size... so be it resultSize = Child wants to be our size,but our size is not fixed. Constrain child to not be bigger than us. resultSize = MeasureSpec.AT_MOST; } Parent asked to see how big we want to be MeasureSpec.UnspecIFIED: Child wants a specific size... let him have it resultSize = Child wants to be our size... find out how big it should be resultSize = VIEw.sUseZeroUnspecifIEdMeasureSpec ? 0 : size; resultMode = MeasureSpec.UnspecIFIED; } Child wants to determine its own size.... find out how big it should be resultSize = VIEw.sUseZeroUnspecifIEdMeasureSpec ? 0 MeasureSpec.UnspecIFIED; } noinspection ResourceType MeasureSpec.makeMeasureSpec(resultSize,resultMode); }
该
方法清楚展示了普通 VIEw 的 MeasureSpec 的创建规则,每个 VIEw 的 MeasureSpec 状态量由其直接父 VIEw 的 MeasureSpec 和 VIEw 自身的属性 LayoutParams (LayoutParams 有宽高尺寸值等信息)共同决定。
子 VIEw 为具体的宽/高
,那么 VIEw 的 MeasureSpec 都为 LayoutParams 中大小。
子 VIEw 为 match_parent
,父元素为精度模式(EXACTLY),那么 VIEw 的 MeasureSpec 也是精准模式他的大小不会超过父容器的剩余空间。
子 VIEw 为 wrap_content
,不管父元素是精准模式还是最大化模式(AT_MOST),VIEw 的 MeasureSpec 总是为最大化模式并且大小不超过父容器的剩余空间。
父容器为 UnspecIFIED 模式主要用于系统多次 Measure 的情形,一般我们不需要关心。
总结为下表:
VIEw.measure() 代码逻辑前面已经分析过了,最终会调用 onMeasuere 方法,下面看下 VIEw.onMeasuere() 的代码:
protected int heightmeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWIDth(),wIDthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightmeasureSpec)); }
上面方法中调用了 方法中调用了 setMeasuredDimension()
方法,setMeasuredDimension()
又调用了 getDefaultSize()
方法。getDefaultSize()
又调用了getSuggestedMinimumWIDth()
和 getSuggestedMinimumHeight(),
那反向研究一下,先看下 getSuggestedMinimumWIDth()
方法 (getSuggestedMinimumHeight()
原理 getSuggestedMinimumWIDth()
跟一样)。
getSuggestedMinimumWIDth() { return (mBackground == null) ? mMinWIDth : max(mMinWIDth,mBackground.getMinimumWIDth()); }
源码很简单,如果 VIEw 没有背景,就直接返回 VIEw 本身的最小宽度 mMinWIDth;
如果给 VIEw 设置了背景,就取 VIEw 本身的最小宽度 mMinWIDth
和背景的最小宽度的最大值.
那么 mMinWIDth
是哪里来的?搜索下源码就可以知道,VIEw 的最小宽度 mMinWIDth
可以有两种方式进行设置:
minWIDth
属性来为 mMinWIDth
赋值:R.styleable.VIEw_minWIDth: mMinWIDth = a.getDimensionPixelSize(attr,1)">); break;第二种是在调用 VIEw 的
setMinimumWIDth
方法为 mMinWIDth
赋值voID setMinimumWIDth( minWIDth) { mMinWIDth = minWIDth; requestLayout();}
下面看下 getDefaultSize() 的代码逻辑:
int getDefaultSize(int size,1)"> measureSpec) { int result = size; MeasureSpec.getMode(measureSpec); MeasureSpec.getSize(measureSpec); MeasureSpec.UnspecIFIED: result = size; MeasureSpec.AT_MOST: MeasureSpec.EXACTLY: result = specsize; result; }
从注释可以看出,getDefaultSize()
这个测量方法并没有适配 wrap_content
这一种布局模式,只是简单地将 wrap_content
跟 match_parent
等同起来。
到了这里,我们要注意一个问题:
getDefaultSize()
方法中 wrap_content
和 match_parent
属性的效果是一样的,而该方法是 VIEw 的 onMeasure()
中默认调用的,也就是说,对于一个直接继承自 VIEw 的自定义 VIEw 来说,它的 wrap_content 和 match_parent 属性是一样的效果,因此如果要实现自定义 VIEw 的 wrap_content
,则要重写 onMeasure()
方法,对 wrap_content
属性进行处理。
如何处理呢?也很简单,代码如下所示:
protected voID onMeasure(int wIDthMeasureSpec,1)"> heightmeasureSpec){ super.onMeasure(wIDthMeasureSpec, heightmeasureSpec); 取得父VIEwGroup指定的宽高测量模式和尺寸 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) { 如果宽高都是AT_MOST的话,即都是wrap_content布局模式,就用VIEw自己想要的宽高值 setMeasuredDimension(mWIDth, mHeight); }else if (wIDthSpecMode ==如果只有宽度都是AT_MOST的话,即只有宽度是wrap_content布局模式,宽度就用VIEw自己想要的宽度值,高度就用父VIEwGroup指定的高度值if (heightSpecMode ==如果只有高度都是AT_MOST的话,即只有高度是wrap_content布局模式,高度就用VIEw自己想要的宽度值,宽度就用父VIEwGroup指定的高度值 setMeasuredDimension(wIDthSpecsize, mHeight); }}
在上面的代码中,我们要给 VIEw 指定一个默认的内部宽/高(mWIDth
和 mHeight
),并在 wrap_content
时设置此宽/高即可。最后将在将宽高设置到 VIEw 上:
// VIEw protected voID setMeasuredDimension(int measureDWIDth,1)"> 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); } voID setMeasuredDimensionRaw( measuredHeight) { mMeasureDWIDth = measureDWIDth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
这里就是把测量完的宽高值赋值给 mMeasureDWIDth
、mMeasuredHeight
这两个 VIEw 的属性,然后将标志位置为已测量状态。
子 VIEw 测量完成以后,会计算 childState,看下 combineMeasuredStates 方法 :
int combineMeasuredStates(int curState,1)"> newState) { return curState | newState; }
当前 curState 为 0, newState 是调用 child.getMeasuredState() 方法得到的,来看下这个方法的具体逻辑:
* Return only the state bits of {@link #getMeasureDWIDthAndState()} * and { #getMeasuredHeightAndState()},combined into one integer. * The wIDth component is in the regular bits { #MEASURED_STATE_MASK} * and the height component is at the shifted bits * { #MEASURED_HEIGHT_STATE_SHIFT}>>{ #MEASURED_STATE_MASK}. getMeasuredState() { return (mMeasureDWIDth&MEASURED_STATE_MASK) | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT) & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT)); }
该方法返回一个 int 值,该值同时包含宽度的 state 以及高度的 state 信息,不包含任何的尺寸信息。
MEASURED_STATE_MASK 的值为 0xff000000,其高字节的 8 位全部为 1,低字节的 24 位全部为 0。
MEASURED_HEIGHT_STATE_SHIFT 值为 16。
将 MEASURED_STATE_MASK 与 mMeasureDWIDth
做与 *** 作之后就取出了存储在宽度首字节中的 state 信息,过滤掉低位三个字节的尺寸信息。
由于 int 有四个字节,首字节已经存了宽度的 state 信息,那么高度的 state 信息就不能存在首位字节。MEASURED_STATE_MASK 向右移 16 位,变成了 0x0000ff00,这个值与高度值 mMeasuredHeight
做与 *** 作就取出了 mMeasuredHeight
第三个字节中的信息。而 mMeasuredHeight
的 state 信息是存在首字节中,所以也得对mMeasuredHeight
向右移相同的位置,这样就把 state 信息移到了第三个字节中。
最后,将得到的宽度 state 与高度 state 按位或 *** 作,这样就拼接成一个 int 值,该值首个字节存储宽度的 state 信息,第三个字节存储高度的 state 信息。
这些都得到之后,就可以开始去计算父 VIEw 的尺寸了:
// 确定父 VIEw 的宽高 setMeasuredDimension(resolveSizeAndState(maxWIDth,1)">resolveSizeAndState(maxHeight,childState << MEASURED_HEIGHT_STATE_SHIFT));
下面开始看 resolveSizeAndState 具体逻辑:
VIEw 的静态方法int resolveSizeAndState(int measureSpec,1)"> childMeasuredState) { MeasureSpec.getSize(measureSpec); result; (specMode) { MeasureSpec.AT_MOST: if (specsize < size) { result = specsize | MEASURED_STATE_TOO_SMALL; } { result = size; } MeasureSpec.EXACTLY: result = specsize; : result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
这个方法的代码结构跟前文提到的 getDefaultSize()
方法很相似,主要的区别在于 specMode
为 AT_MOST 的情况。我们当时说 getDefaultSize()
方法是没有适配wrap_content
这种情况,而这个 resolveSizeAndState()
方法是已经适配了 wrap_content
的布局方式,那具体怎么实现 AT_MOST 测量逻辑的呢?有两种情况:
当父 VIEwGroup 指定的最大尺寸比 VIEw 想要的尺寸还要小时,会给这个父 VIEwGroup 的指定的最大值 specsize
加入一个尺寸太小的标志 MEASURED_STATE_TOO_SMALL,然后将这个带有标志的尺寸返回,父 VIEwGroup 通过该标志就可以知道分配给 VIEw 的空间太小了,在窗口协商测量的时候会根据这个标志位来做窗口大小的决策。
当父 VIEwGroup 指定的最大尺寸比没有比 VIEw 想要的尺寸小时(相等或者 VIEw 想要的尺寸更小),直接取 VIEw 想要的尺寸,然后返回该尺寸。
getDefaultSize()
方法只是 onMeasure()
方法中获取最终尺寸的默认实现,其返回的信息比 resolveSizeAndState()
要少,那么什么时候才会调用 resolveSizeAndState()
方法呢? 主要有两种情况:
AndroID 中的大部分 VIEwGroup 类都调用了 resolveSizeAndState()
方法,比如 linearLayout 在测量过程中会调用 resolveSizeAndState()
方法而非 getDefaultSize()
方法。
我们自己在实现自定义的 VIEw 或 VIEwGroup 时,我们可以重写 onMeasure()
方法,并在该方法内调用 resolveSizeAndState()
方法。
到此,终于把 VIEw 测量过程讲完了。
下一篇开始讲 VIEw 的 layout 和 draw 过程。
参考文章Android源码完全解析——View的Measure过程View的绘制流程 总结
以上是内存溢出为你收集整理的Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp全部内容,希望文章能够帮你解决Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)