View的Measure过程解析
来源:互联网 发布:python 编码问题 编辑:程序博客网 时间:2024/05/16 15:20
背景
在《Activity.setContentView()内部实现解析》分析了View是如何被添加到DecorView中,但添加到DecorView后,View对用户来说还是不可见的。View在呈现给用户前,它需要经过Measure -> Layout -> Draw三个步骤。本文将从DecorView的源码开始,分析View的Measure过程。
预备知识–测量概述
在分析测量过程前需要确定测量的意义和我对测量过程的定义。View在呈现出来前,需要确定的第一个问题是:显示的View多大?即View的大小,而Measure就是为了确定View的大小。那View的大小是由父容器(提示:父容器是指包含该View的父布局)来决定还是由View自己决定呢?
现在我把Measure过程分为两个阶段
- 第一阶段:父容器计算出measureSpec值,这个值就是父容器对子View的显示区域的限定条件,并传递给子View
- 第二阶段:子View根据显示区域限定条件measureSpec来设置自身大小,当然View也可根据自己的需要设置大小
提示:measureSpec是从父容器传递给子View的,我们可以把measureSpec定义为父容器父子View显示范围的限定条件。
父容器有权利限定子View的显示范围,即预留多少空间给子View。而父容器对子View的限定条件就承载在measureSpec值中。所以在分析过程中说到“显示范围限定条件”指的就是measureSpec值
通过下面的图来说明我对View测量过程的划分
可得出下面的结论:
父容器传递给子View的measureSpec只是规定了子View的可显示区域,而View的实际显示大小还是得由View来自己设定,View可以根据measureSpec来设置大小,也可随意设置,当然如果子View设定的大小超出父容器规定的范围,那就可以,只是父容器给你预留的空间无法显示完全子View。
预备知识–MeasureSpec
在测量概述中说到父容器会给子View传递显示范围限定条件measureSpec。
现在来分析一下measureSpec是如何承载显示范围限定条件的。
measureSpec是int类型变量,在java中int类型由4个字节组成,每个字节8位,所以measureSpec一共有32位,这32位由两部份组成:specMode和specSize,前面的两位是specMode标志位, 后面30位是specSize,如下图所示:
系统提供MeasureSpec工具类,使得我们可以很方便的从measureSpec中取出specSize和specMode
int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);// MeasureSpce的内部实现int specMode = widthMeasureSpce & 0xB000; // 0xB000的二进制为1100 0000 0000 0000int specSize = widthMeasureSpec & 0x3fff; // 0x3fff的二进制为0011 1111 1111 1111
specMode有三种模式,不同的模式对specSize有不同的解释
这里列出specMode的每个模式,以及每种模式对specSize的解释
对于MeasureSpec这个值是如何计算出来的,我们在下面会详细的分析,你只要知道,父容器会给子View传递显示范围限制条件measureSpec,而这个显示范围限制条件是以一个int类型的变量measureSpec
分析
《Activity.setContentView()内部实现解析》中说到了在ViewRootImpl.setView()方法中,会调用WindowSession.addToDisplay( )方法通知WindowManagerService添加一个Window,WindowManagerService进行相应的逻辑处理后,会通知ViewRootImpl对象对DecorView进行布局,而布局的入口函数是performTraversals( ),该方法代码量很长,但我们只需关注对DecorView开始测量的入口。
提示:由于DecorView是最顶级的View,所以DecorView不存在真正的父容器,在后面的分析中我们说到的DecorView的父容器指的就是ViewRootImpl这个类,虽然这个类不属于View,但由于ViewRootImpl是直接管理DecorView的,所以就把ViewRootImpl当做DecorView的父容器
private void performTraversals() { 省略.... // frame是Rect类对象,这里我们只需知道frame是系统给定的用于显示DecorView的区域大小 mWidth = frame.width(); mHeight = frame.height(); // lp是前面为DecorView创建的布局参数,这里lp.with和lp.height都是MATCH_PARENT // 通过getRootMeaureSpec()方法确定DecorView的childWidthMeasureSpec和childHeightMeasureSpec // 即DecorView的显示范围限定条件 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); // 开始测量DecorView的大小 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 省略....}
从上面的源码中可知道getRootMeasureSpec( )方法确定了DecorView的显示范围限定条件,在分析getRootMeasureSpec( )的实现之前我们来猜测一下,这个显示范围限定条件是如何计算出来的?是DecorView来决定还是由系统来决定呢?
getRootMeasureSpec(int windowSize, int rootDimension)方法的windowSize参数是系统给定的窗口大小,rootDimension是DecorView的layout_width/layout_height属性值。我们直接查看getRootMeasureSpec()方法的源码实现。
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: //DecorView希望它的大小与系统给定的显示大小一样 // ViewRootImpl就会创建一个显示范围限定条件为“你可设置你的大小为windowSize”的measureSpec // 并传递给DecorView measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // DecorView希望它的大小可以足够显示DecorView中包含的内容 // ViewRootImpl就会创建显示范围限定条件为“你可根据需要设置你的大小,但最大不能超过windowSize”的measureSpec // 并传递给DecorView measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // ViewRootImpl就会创建显示范围限定条件为“你可设置你的大小为rootDimension”的measureSpec // 并传递给DecorView measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec;}
从上面的源码可以得出一下结论:对DecorView的MeasureSpec(显示范围的限定条件)是由系统和DecorView的布局参数layout_width/layout_height共同决定,对,共同决定,而不是由DecorView自己来决定,也不是由系统自己来决定。
下面用表格整理生成DecorView的MeasureSpec的不同组合
在performMeasure( )内部调用measure()进行测量DecorView,由于DecorView没有重写(也不可以重写)measure方法,所以进入View.mesure()方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { }}// widthMeasureSpec和heightMeasureSpec就是从父容器传递给子View的“显示范围限定条件”public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); ...省略 if (forceLayout || needsLayout) { int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); // 最后还是在onMeasure()方法中设置View的大小 onMeasure(widthMeasureSpec, heightMeasureSpec); ...省略 } ...省略 }
onMeasure这个方法很重要,onMeasure()是View中的方法,子类可以重写onMeasure()来根据自己的需求来设置自身的大小,但如果不重写onMeasure( )也没有问题,View.onMeasure( )方法会根据widthMeasureSpec和heightMeasureSpec这两个显示范围限定条件来设置View的大小。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 最终调用setMeasuredDimension()方法给mMeasuredWidth和mMeasuredHeight这两个变量赋值 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}// View建议的自己的最小宽度,由于View这个时候还没有内容,所以只能由View的背景来决定protected int getSuggestedMinimumWidth() { // 最小推荐值是由mBackground和mMinWidth决定,这两个属性值都是在创建View时设定的 return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}// size: View建议的最小值,通过getSuggestedMinimumWidth()方法获取, // measureSpec: 是父容器传递给View的限定条件public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { // 父容器的告诉子View:你自己来决定你的大小吧,我不限制你的大小 // 这时View就会使用自己建议最小值,因为View目前还不知它的内部包含什么,所以只能用建议值 case MeasureSpec.UNSPECIFIED: result = size; break; // 如果父容器告诉子View:你自己来决定你的大小吧,但你设置的大小不能超过specSize // 或者告诉子View:设置specSize这个值为你的大小吧 // 子View收到父容器的上面两种限定条件都会把大小设置为specSize // 注意:子View收到父容器“你自己来决定你的大小吧,但你设置的大小不能超过specSize”这个限定是,也会直接把大小设置为specSize case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result;}
下面用一个表格来整理出View.onMeasure( )方法内部是如何根据MeasureSpec来设定大小的
从View派生出来的子类当然可以不按上面这种方法来确定自己的大小,那就需要重写onMeasure( ),并根据自己的需求来设置大小。比如:DecorView、FrameLayout . . .
我们来看一下DecorView重写的onMeasure( )方法
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ...(此处就是根据一些属性调整一下withMeasureSpec和heightMeasureSpec,这里直接省略) // 调用父类的onMeasure()继续进行测量 super.onMeasure(widthMeasureSpec, heightMeasureSpec);}
我们都知道DecorView是从FrameLayout这个布局容器继承来,所以最终还是调用FrameLayout.onMeasure( )
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; // 遍历DecorView中包含的子View,并通过measureChildWithMargins()方法来测量每个子View的大小 for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { // 测量child大小,下面会对这个方法进行分析 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); // maxWidth和maxHeight记录最大那个View的长宽, 注意还需要加上View设定的layout_margin值 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); } } // 加上DecorView设定的padding值 maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); ... // 最后通过resolveSizeAndState()方法来计算DecorView的长宽 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); ...}
我们先不分析measureChildWithMargins( )方法内部是如何对子View进行测量的,measureChildWithMargins( )测量完毕后,我们就可通过getMeasureWidth( )和getMeasureHeight( )来获取子View的测量结果。当DecorView测量完毕子View后,接下来就是确定DecorView(即子View的父容器)的大小,我们来分析resolveSizeAndState( )这个方法是如何确定DecorView的大小
// size为DecorView中包含的最大的子View的大小// measureSpec为DecorView的父容器(由于DecorView是最顶级的View所以不存在父容器,我们说DecorView的父容器指的就是ViewRootImpl)传递给DecorView的显示范围限定条件public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { // 父容器限定条件为:"由你自己来确定你的大小" case MeasureSpec.UNSPECIFIED: result = size; break; // 父容器限定条件为:"你可以自己设置你的大小,但不要超过specSize" // 这时DecorView的大小设置为specSize和size中的最小值,这样就确保DecorView的大小不会超过specSize case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; // 父容器限定条件为:"你可把specSize设置为你的大小值" case MeasureSpec.EXACTLY: result = specSize; break; } return result | (childMeasuredState&MEASURED_STATE_MASK); }
注意:resolveSizeAndState( )方法的逻辑是确定FrameLayout布局的大小,但是对于其它的一些布局如LinearLayout、RelativeLayout就是另外一套处理逻辑
分析完DecorView大小的确定之后,我们返回去分析子View是如何测量的
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { // 获取子View的Margin值参数 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 通过getChildMeasureSpec()方法获取childWidthMeasureSpec和childHeightMeasureSpec // childWidthMeasureSpec和childHeightMeasureSpec就是父容器(DecorView)即将要传递给子View的参数,即显示范围限定条件 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); // 把childHeightMeasureSpec和childWidthMeasureSpec传递给子View,让子View根据这两个限定值来设置自己的大小 // measure的处理逻辑前面已经分析过了 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}// 这个方法很重要,该方法确定子View的显示范围限定条件// spec 显示范围限定条件// padding 包括了父容器的padding值和子View的layout_margin值// childDimension 子View在布局资源文件中或者ViewGroup.LayoutParam设置的layout_width或layout_height值public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); // 先减掉父容器设定的Padding值和View设定的Margin值,即剩余的才是用于给View的范围 int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // 若父容器(DecorView)传递的限定条件为:"你可把specSize设置为你的大小值",即父容器希望把specSize + MeasureSpec.EXACTLY这个限定范围传递给子View // 但是子View还是会根据自己的layout_width/layout_height属性值,进一步作出确定,所以还会根据childDimension值才能确定 case MeasureSpec.EXACTLY: if (childDimension >= 0) { // 如果layout_width/layout_height属性设定了一个特定的值childDimension // 那子View的显示范围限定条件为: childDimension + MeasureSpec.EXACTLY // 即翻译为:"你可以把你的大小设定特定的大小值childDimension,这个值是你的在layout_width属性设置的" resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 如果layout_width/layout_height属性设置为LayoutParams.MATCH_PARENT // 那子View的显示范围限定条件为: size + MeasureSpec.EXACTLY // 即翻译为:"你可以把你的大小设定特定的大小值size,这个值是父容器的大小" resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 如果layout_width/layout_height属性设置为LayoutParams.WRAP_CONTENT // 那子View的显示范围限定条件为: size + MeasureSpec.AT_MOST // 即翻译为:"你可随便设置你的大小,但不能超过size,这个值是父容器的大小" resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; case MeasureSpec.AT_MOST: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}
总结
View的测量过程已分析完毕,最后总结一下我在开发过程遇到的一些问题
- 直接看代码
FrameLayout mPreviewContainer = findViewById(R.id.container);
mPreviewContainer.measure(0, 0);
上面代码能正确计算出mPreviewContainer的measureWidth和measureHeight吗?
- View的Measure过程解析
- View的measure过程
- View 的 Measure 过程
- view的measure过程
- View的measure过程
- 源码解析Android中View的measure量算过程
- 源码解析Android中View的measure量算过程
- 源码解析Android中View的measure量算过程
- 源码解析Android中View的measure量算过程
- 018.View的Measure过程
- android View measure过程源码解析
- 【自定义view系列】View的measure过程
- 浅析Android View的Measure过程
- Android应用程序窗口View的measure过程
- Android View的Measure过程(一)
- 从源码分析View的measure过程
- View和ViewGroup 的measure过程
- Android View的measure过程详解
- 利用Spring AOP自定义注解解决日志和签名校验 详解
- Android加载框
- java基础
- [机器学习入门] 李宏毅机器学习笔记-8(Backpropagation;反向传播算法)
- 101 数组在什么时候会转换为指针
- View的Measure过程解析
- Android第二天 线性布局(LinearLayout)
- CGI
- Android 注释 收集
- 102 C语言指针数组(每个元素都是指针)
- Educational Codeforces Round 22 B. The Golden Age(枚举)
- 103 一道题目玩转指针数组和二级指针
- 文章标题
- Android (线性、表格、网格、帧)布局小结