View那些事儿(2) -- 理解MeasureSpec
来源:互联网 发布:java语言 编辑:程序博客网 时间:2024/06/03 14:22
View的绘制的三大流程的第一步就是Measure(测量),想要理解View的测量过程,必须要先理解MeasureSpec,从字面上看,MeasureSpec就是“测量规格”的意思。其实它在一定程度上决定了View的测量过程,具体来讲它包含了一个View的尺寸规格信息。在系统测量View的尺寸规格的时候会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据MeasureSpec测量出View的宽高。
一.MeasureSpec的组成
MeasureSpec代表了一个32位的int值,高2位代表了SpecMode(测量模式),低30位代表了SpecSize(规格大小)。下面结合一段源码来分析:
public static class MeasureSpec { private static final int MODE_SHIFT = 30;//二进制数左移的位数 private static final int MODE_MASK = 0x3 << MODE_SHIFT;//将二进制数11左移动30位 public static final int UNSPECIFIED = 0 << MODE_SHIFT;//将二进制数00左移动30位 public static final int EXACTLY= 1 << MODE_SHIFT;//将二进制数01左移动30位 public static final int AT_MOST= 2 << MODE_SHIFT;//将二进制数10左移动30位 //将SpecMode和SpecSize包装成MeasureSpec public static int makeMeasureSpec(int size, int mode ){ if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } //从mesureSpec中取出SpecMode public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } //从measureSpec中取出SpecSize public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }}
通过以上源码,便可以很清晰地看出整个MeasureSpec的工作原理,其中涉及到了java里的按位运算操作,“&”是按位与的意思,“~”则是按位取反的意思,“<<”是左移运算符(x<<1就表示x的值乘2),这些运算符都是在2进制数的层面上进行运算的,具体的运算规则google上面一大堆,这里就不多说了。
MeasrureSpec类的三个常量:UNSPECIFIED、EXACTLY、AT_MOST分别代表的三种SpecMode(测量模式):
- UNSPECIFIED(不指定大小的)
父容器不对View有任何限制,想要多大有多大,这种情况一般属于系统内部,表示一种测量状态,几乎用不到。例如:系统对ScrollView的绘制过程。 - EXACTLY(精确的)
父容器已经检测出View所需要的精确的大小,这个时候View的最终大小就是SpecSize所指定的值。这里对应于:LayoutParams中的match_parent和具体的数值,如20dp。 - AT_MOST(最大的)
这种模式稍微难处理一点,它指的是父容器指定了一个可用的大小(SpecSize),View的大小不能超过这个值的大小。如果超过了,就取父容器的大小,如果没超过,就取自身的大小。这里对应于:LayoutParams中的wrap_content模式。
全部都是些理论,没点demo怎么行,下面直接上一段代码吧:
首先,要实现的效果很简单,就是一个圆形的自定义View(我们主要是要处理wrap_content的情况下的数据,必须给它一个默认值):
public class CircleView extends View { Paint mPaint;//画笔类 public CircleView(Context context) { super(context); } public CircleView(Context context, AttributeSet attrs) { super(context, attrs); } public CircleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public CircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //第一步肯定是拿到View的测量宽高(SpecSize)和测量模式(SpecMode) int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //View显示的时候的实际的大小 int width = 0; int height = 0; //开始处理宽度 //默认的宽度(在wrap_content的情况下必须有个默认的宽高) int defaultWidth = 200; //判断SpecMode(在xml中指定View的宽度的时候就已经决定了View的SpecMode) switch (widthMode){ case MeasureSpec.AT_MOST://这里指的是wrap_content,在这个模式下不能超过父容器的宽度 width = defaultWidth; break; case MeasureSpec.EXACTLY://这里指的是match_parent或者具体的值,不需要做什么处理,width直接等于widthSize就可以了 width = widthSize; break; case MeasureSpec.UNSPECIFIED://这个模式用不到,完全可以忽略 width = defaultWidth; break; default: width = defaultWidth; break; } //开始处理高度 int defaultHeight = 200; switch (heightMode){ case MeasureSpec.AT_MOST://这里指的是wrap_content,在这个模式下不能超过父容器的高度 height = defaultHeight; break; case MeasureSpec.EXACTLY://这里指的是match_parent或者具体的值,不需要做什么处理,height直接等于heightSize就可以了 height = heightSize; break; case MeasureSpec.UNSPECIFIED://这个模式用不到,完全可以忽略 height = defaultHeight; break; default: height = defaultHeight; break; } //最后必须调用父类的测量方法,来保存我们计算的宽度和高度,使得设置的测量值生效 setMeasuredDimension(width,height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //初始化画笔,并进行一系列的设置,如颜色等。 mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setAntiAlias(true);//设置抗锯齿 //canvas.drawCircle的几个参数分别是:圆心的x坐标,y坐标,半径,画笔 canvas.drawCircle(getWidth()/2,getHeight()/2,Math.min(getWidth()/2,getHeight()/2),mPaint); }}
显示效果如下:
大家可以试一下,如果把不在onMreasure中做以上处理(注释掉onMeasure方法即可看见效果),给CircleView设置的wrap_content会失效,实际的显示效果和match_parent没什么区别。
在这个地方提前写了一个自定义View是为了帮助读者理解MeasureSpec的相关用法,详细结合前一篇文章的讲解加上代码的注释,大家还是能很容易看懂的。
当然为了把这个过程表述清楚,我把代码写得很详细,显得有点累赘,实际中其实可以不用这么详细。
二.MeasureSpec和LayouParams的关系
上面都在讲MeasureSpec是什么,现在就该说说他是怎么来的了。
这里要分两部分说:第一是普通的View,第二是DecorView
- 在普通View测量过程中,系统会将View自身的LayoutParams在父容器的约束下转换为对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽和高;
- 在DecorView(顶级View)的测量过程中,系统会将View自生的LayoutParams在窗口尺寸的约束下转换为对应的MeasureSpec 。
1.首先,重点说一下普通的View
对于普通的View(即在布局文中的View)来说,它的measure过程由ViewGroup传递而来,所以先来看看ViewGroup的measureChildWithMargins()方法:
//对ViewGroup的子View进行Measure的方法protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { //获取子View的布局参数信息(MarginLayoutParams是继承自ViewGroup.LayoutParmas的) final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //获取子View的宽度的MeasureSpec,需要传入父容器的parentWidthMeasureSpec等信息 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); //获取子View的高度的MeasureSpec,需要传入父容器的parentHeightMeasureSpec等信息 final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
结合注释,从上面这段测量子View的MeasureSpec的源码中可以看出,在测量的时候传入了父容器的MeasureSpec信息和子View自身的LayoutParams信息(如margin、padding),所以才说普通View的测量过程与父容器和自身的LayoutParams有关。
那么像知道测量的具体过程就得看看getChildMeasureSpec()这个方法了(看似代码较多,但是很简单):
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //还是先拿到specMode和specSize信息 int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //padding指的是父容器已经占据的空间大小,所以,子View的大小因为父容器的大小减去padding int size = Math.max(0, specSize - padding); //测量后View最终的specSize和specMode int resultSize = 0; int resultMode = 0; //针对三种specMode进行判断 switch (specMode) { // Parent has imposed an exact size on us case 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 = MeasureSpec.EXACTLY; } 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; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: 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. 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; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
上面的代码很清晰地展示这个测量过程,下面一个表格来梳理具体的判断逻辑。(parentSize是指父容器目前可使用的大小)
childLayoutParams↓
childSize EXACTLY
childSize EXACTLY
childSize match_parent EXACTLY
parentSize AT_MOST
parentSize UNSPECIFIED
0 wrap_content AT_MOST
parentSize AT_MOST
parentSize UNSPECIFIED
0
根据表格,getChildMeasureSpec()方法的判断规则一目了然:
- 当View的指定了确定的宽高的时候,无论父容器的SpecMode是什么,它的SpecMode都是EXACTLY,并且大小遵循LayoutParams中的大小;
- 当View的宽/高指定成match_parent的时候,它的SpecMode与父容器相同,并且大小不能超过父容器的剩余空间大小;
- 当View的宽/高指定成wrap_content的时候,它的SpecMode恒为(不考虑UNSPECIFIED的情况)AT_MOST,并且大小不能超过父容器的剩余空间大小。
由于UNSPECIFIED模式我们一般接触不到,故在这里不做讨论
从上面的总结来看,其实普通View的MeasureSpec的LayoutParams的关系还是很容易理解与记忆的。
2.下面该来看看DecorView(顶级View)了
对于DecorView来说,MeasureSpec是由窗口尺寸和自身的LayoutParams共同决定的。
还是来看看源码吧,在ViewRootImpl(这个类被隐藏了,需要手动搜索sdk目录找出ViewRootImpl.java才能看见源码)有一个meaureHierarchy()方法,其中有下面这段代码:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
其中,desiredWindowWidth和desiredWindowHeight分别指的是屏幕的宽高。
看到这里就隐约感觉到DecorView的MeasureSpec会和窗口尺寸有关,再来看看getRootMeasureSpec就更明了了:
private 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; }
看到这里就豁然开朗了,DecorView的MeasureSpec的确和窗口尺寸有关。
所以,Decor的MeasureSpec根据它的LayoutParams遵循以下规则:
- LayoutParams.MATCH_PARENT: EXACTLY(精准模式),大小就是窗口的大小;
- LayoutParams.WRAP_CONTENT: AT_MOST(最大模式),大小不定,但是不能超过窗口的大小;
- 固定的大小(如200dp): EXACTLY(精准模式),大小为LayoutParams所制定的大小。
至此,对MeasureSpec的理解就结束了
- View那些事儿(2) -- 理解MeasureSpec
- 自定义View准备:MeasureSpec理解
- View的工作原理 理解MeasureSpec
- View.MeasureSpec
- 深入理解Oracle表(2):驱动表的那些事儿
- 深入理解Oracle表(2):驱动表的那些事儿
- 理解MeasureSpec
- 快速理解android View的测量onMeasure()与MeasureSpec
- View那些事儿(1) -- View绘制的整体流程
- View的MeasureSpec使用
- 【view】MeasureSpec介绍
- 解析View中的MeasureSpec
- 【Android基础】-View.MeasureSpec
- Android View MeasureSpec详解
- 明朝那些事儿4-2
- IHE那些事儿(2)
- ssh那些事儿(2)-实战
- javaScript对象那些事儿(2)
- springboot与mybatis整合操作数据库
- 三次握手和四次挥手
- Spring配置文件以及基本常识
- spring boot发布war包,部署到外部tomcat服务器
- Tensorflow模型Android上的使用
- View那些事儿(2) -- 理解MeasureSpec
- 计算机网络--ARP地址解析协议详解
- HDU6153 A Secret 扩展KMP
- Java标识符
- 2017ccpc网络赛——Friend-Graph
- 分治算法详解
- JavaScript-打开新窗口(window.open)
- vm中的新生代Eden和survivor区
- hibernate Mysql 自增长 注解配置,表无关联的注解方式关联查询