前言
上一篇咱们讲到最根本的android运行流程和绘制流程的调用程序,那么咱们最终失去的一个论断是activity的生命周期是有零碎服务所触发,由零碎服务发动handle调用到handleResumeActivity()
开始绘制流程而后最终交由ViewRootImpl
调用到performTraversals()
而后顺次之行了咱们UI的理论绘制流程measure(测量),layout(布局摆放),Draw(具体绘制)
那么明天咱们须要理解的是对UI具体的绘制流程measure,layout,Draw
进行深入分析,并且依靠于明天所学内容实现自定义瀑布流式布局!
1.View的测量
首先咱们找到了绘制流程当中performTraversals()的测量布局办法
if (!mStopped || mReportNextDraw) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth=" + mWidth + " measuredWidth=" + host.getMeasuredWidth() + " mHeight=" + mHeight + " measuredHeight=" + host.getMeasuredHeight() + " coveredInsetsChanged=" + contentInsetsChanged); // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // Implementation of weights from WindowManager.LayoutParams // We just grow the dimensions as needed and re-measure if // needs be int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); boolean measureAgain = false; if (lp.horizontalWeight > 0.0f) { width += (int) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain = true; } if (lp.verticalWeight > 0.0f) { height += (int) ((mHeight - height) * lp.verticalWeight); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); measureAgain = true; } if (measureAgain) { if (DEBUG_LAYOUT) Log.v(mTag, "And hey let's measure once more: width=" + width + " height=" + height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } layoutRequested = true; } }
performMeasure办法,在这里他与调用到的
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}
在这里咱们能够看到以后他调用了view当中的测量,那么这里我门次要是对于测量方法当中的代码进行剖析,在调用performMeasure
调用之前须要两个参数
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
他这里的意思是,传入宽高的测量规格,那么这个规格是什么意思?那么进入到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;}
而后在这里我门看到了一个对象MeasureSpec
,MeasureSpec
的作用是在在Measure
流程中,零碎将View的LayoutParams
依据父容器所施加的规定转换成对应的MeasureSpec(规格),而后在onMeasure中依据这个MeasureSpec来确定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); } //...}测量模式 EXACTLY :父容器曾经测量出所须要的准确大小,这也是childview的最终大小 ------match_parent,准确值是爸爸的 ATMOST : child view最终的大小不能超过父容器的给的 ------wrap_content 准确值不超过爸爸 UNSPECIFIED: 不确定,源码外部应用 -------个别在ScrollView,ListView
那么在此处咱们能够看到, 在出咱们测量方法当中他传递的并不是一个纯正的size,而是遵循了咱们设置宽高时的几种模式:EXACTLY
,ATMOST
,UNSPECIFIED
这三个模式的实质是0,1,2的左位移30位
那么其实咱们先能了解为咱们实际上在传递值得过程当中将显示模式+size打包一起交给measure
办法,而外面的数据结构其实实际上是一个32位的数值,咱们能够显著看到几个模式的值在前面进行了左位移操作了30位,用MODE_SHIFT 操作之后,理论表明一个32位的值30前两位作为MODE
,而MODE_MASK
示意是后30位,所以当初能得出一个论断他们的数据构能够看成是
而这个时候咱们看到有三个办法负责打包解析
- makeMeasureSpec--负责打包mode和size
- getMode--负责解析失去mode局部
- getSize--负责解析失去size局部
那么打包时使用了(size & ~MODE_MASK) | (mode & MODE_MASK)的算法进行混合,那么这里咱们能够认为 是size 转化成32位后放入后30位 组合 mode(mode放入前两位)
getMode解析用了measureSpec & MODE_MASK(解析只有前2位)
getSize解析用了measureSpec & ~MODE_MASK(不要前两位)
那么此处就能够失去,在测量时他真正的给我传递的是规格,而所谓的规格只不过是显示模式+理论宽高的一个数据包,或者你了解为这个值当中蕴含模式和具体数值就行了
所以咱们得出一个论断,View的测量流程中,通过makeMeasureSpec
来保留宽高信息,在其余流程通过getMode或getSize
失去模式和宽高。那么问题来了,下面提到MeasureSpec
是子容器和父容器的模式所独特影响的,那么,对于DecorView
来说,它曾经是顶层view
了,没有父容器,那么它的MeasureSpec
怎么来的呢? 是否还记得咱们的setContent
所加载的零碎布局?
咱们的初始就曾经给了一个布局去装载,所以,在这里,他的爸爸是零碎布局
那么到目前为止,就曾经取得了一份DecorView
的MeasureSpec
,它代表着根View的规格、尺寸,在接下来的measure
流程中,就是依据已取得的根View的MeasureSpec
来逐层测量各个子View。来到performMeasure
办法,看看它做了什么工作
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}
这里很显著他间接调用搞得是view的measure这里的mView就是DecorView,也就是说,从顶级View开始了测量流程
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { ... if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { ... if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } ... }
能够看到,它在外部调用了onMeasure
办法所以这里开始调用onMeasure
,那么留神,到此为止,咱们的布局容器的外围就在这里了,不论是LinearLayout
或者是FreamLayout
还是其余布局, 他们都是通过测量组件,实现咱们的布局定位,每一个Layout
的onMeasure
实现都不一样,这里因为顶层是一个FreamLayout
所以咱们
参照FreamLayout作为案例 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//获取以后布局内的子View数量int count = getChildCount();//判断以后布局的宽高是否是match_parent模式或者指定一个准确的大小,如果是则置measureMatchParent为false.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的子Viewfor (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { //对每一个子View进行测量 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //寻找子View中宽高的最大者,因为如果FrameLayout是wrap_content属性 //那么它的大小取决于子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()); //如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中增加 //宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响 if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } }}// Account for padding toomaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// Check against our minimum height and widthmaxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// Check against our foreground's minimum height and widthfinal 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));//子View中设置为match_parent的个数count = mMatchParentChildren.size();//只有FrameLayout的模式为wrap_content的时候才会执行下列语句if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //对FrameLayout的宽度规格设置,因为这会影响子View的测量 final int childWidthMeasureSpec; /** * 如果子View的宽度是match_parent属性,那么对以后FrameLayout的MeasureSpec批改: * 把widthMeasureSpec的宽度规格批改为:总宽度 - padding - margin,这样做的意思是: * 对于子Viw来说,如果要match_parent,那么它能够笼罩的范畴是FrameLayout的测量宽度 * 减去padding和margin后剩下的空间。 * * 以下两点的论断,能够查看getChildMeasureSpec()办法: * * 如果子View的宽度是一个确定的值,比方50dp,那么FrameLayout的widthMeasureSpec的宽度规格批改为: * SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式 * * 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格批改为: * SpecSize为子View的宽度减去padding减去margin,SpecMode为AT_MOST模式 */ 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须要从新进行measure过程 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }}
总结一下:
- 首先,FrameLayout依据它的MeasureSpec来对每一个子View进行测量,即调用
measureChildWithMargin
办法,这个办法上面会具体阐明; - 对于每一个测量实现的子View,会寻找其中最大的宽高,那么FrameLayout的测量宽高会受到这个子View的最大宽高的影响(wrap_content模式),接着调用setMeasureDimension办法,把FrameLayout的测量宽高保留。
- 最初则是非凡状况的解决,即当FrameLayout为wrap_content属性时,如果其子View是match_parent属性的话,则要从新设置FrameLayout的测量规格,而后从新对该局部View测量。
在下面提到setMeasureDimension办法,该办法用于保留测量后果,在下面的源码外面,该办法的参数接管的是resolveSizeAndState办法的返回值那么咱们间接看View#resolveSizeAndState办法:
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {final int specMode = MeasureSpec.getMode(measureSpec);final int specSize = MeasureSpec.getSize(measureSpec);final int result;switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size;}return result | (childMeasuredState & MEASURED_STATE_MASK); }
能够看到该办法的思路是相当清晰的,当specMode是EXACTLY时,那么间接返回MeasureSpec外面的宽高规格,作为最终的测量宽高;当specMode时AT_MOST时,那么取MeasureSpec的宽高规格和size的最小值
2.子View测量
下面在FrameLayout测量内提到的measureChildWithMargin办法,它接管的主要参数是子View以及父容器的MeasureSpec,所以它的作用就是对子View进行测量,那么咱们间接看这个办法
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);child.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 1 }
外面调用了getChildMeasureSpec办法,把父容器的MeasureSpec以及本身的layoutParams属性传递进去来获取子View的MeasureSpe,在这里咱们能够看到间接又调用了子类的measure
总结
那么当初咱们能失去整体的测量流程:
在performTraversals开始取得DecorView种的零碎布局的尺寸,而后在performMeasure办法中开始测量流程,对于不同的layout布局有着不同的实现形式,但大体上是在onMeasure办法中,对每一个子View进行遍历,依据ViewGroup的MeasureSpec及子View的layoutParams来确定本身的测量宽高,而后最初依据所有子View的测量宽高信息再确定爸爸的宽高
一直的遍历子View的measure办法,依据ViewGroup的MeasureSpec及子View的LayoutParams来决定子View的MeasureSpec,进一步获取子View的测量宽高,而后逐层返回,一直保留ViewGroup的测量宽高。
看完三件事❤️
如果你感觉这篇内容对你还蛮有帮忙,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我发明的能源。
- 关注公众号 『 小新聊Android 』,不定期分享原创常识。
- 同时能够期待后续文章ing
- 关注后回复【666】扫码即可获取Android进阶学习材料包