写点什么

Android 自定义 View:MeasureSpec 的真正意义与 View 大小控制

  • 2021 年 11 月 05 日
  • 本文字数:3214 字

    阅读完需:约 11 分钟

  • AT_MOST(至多),父控件为子元素指定最大参考尺寸,希望子 View 的尺寸不要超过这个尺寸,跟上面场景 3 比较相似。这种模式也是父控件根据自身的 MeasureSpec 跟子 View 的布局参数来确定的,一般是子 View 的布局参数采用 wrap_content 的时候。


先来看一下 ViewGroup 源码中 measureChild 怎么为子 View 构造 MeasureSpec 的:


protected void measureChild(View child, int parentWidthMeasureSpec,


int parentHeightMeasureSpec) {


final LayoutParams lp = child.getLayoutParams();


final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,


mPaddingLeft + mPaddingRight, lp.width);


final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,


mPaddingTop + mPaddingBottom, lp.height);


child.measure(childWidthMeasureSpec, childHeightMeasureSpec);


}


由于任何 View 都是支持 Padding 参数的,在为子 View 设置参考尺寸的时候,需要先把自己的 Padding 给去除,这同时也是为了 Layout 做铺垫。接着看如何 getChildMeasureSpec 获取传递给子 View 的 MeasureSpec 的:


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) {


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;


}


return MeasureSpec.makeMeasureSpec(resultSize, resultMode);


}


可以看到父控件会参考自己的 MeasureSpec 跟子 View 的布局参数,为子 View 构建合适的 MeasureSpec,盗用网上的一张图来描述就是



当子 View 接收到父控件传递的 MeasureSpec 的时候,就可以知道父控件希望自己如何显示,这个点对于开发者而言就是 onMeasure 函数,先来看下 View.java 中 onMeasure 函数的实现:


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),


getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));


}


其中 getSuggestedMinimumWidth 是根据设置的背景跟最小尺寸得到一个备用的参考尺寸,接着看 getDefaultSize,如下:


public static int getDefaultSize(int size, int measureSpec) {


int result = size;


int specMode = MeasureSpec.getMode(measureSpec);


int specSize = MeasureSpec.getSize(measureSpec);


switch (specMode) {


case MeasureSpec.UNSPECIFIED:


result = size;


break;


case MeasureSpec.AT_MOST:


case MeasureSpec.EXACTLY:


result = specSize;


break;


}


return result;


}


可以看到,如果自定义 View 没有重写 onMeasure 函数,MeasureSpec.AT_MOST 跟 MeasureSpec.AT_MOST 的表现是一样的,也就是对于场景 2 跟 3 的表现其实是一样的,也就是 wrap_content 就跟 match_parent 一个效果,现在我们知道 MeasureSpec 的主要作用:**_父控件


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


传递给子 View 的参考_**,那么子 View 拿到后该如何用呢?


自定义 View 尺寸的确定


============


接收到父控件传递的 MeasureSpec 后,View 应该如何用来处理自己的尺寸呢?onMeasure 是 View 测量尺寸最合理的时机,如果 View 不是 ViewGroup 相对就比较简单,只需要参照 MeasureSpec,并跟自身需求来设定尺寸即可,默认 onMeasure 的就是完全按照父控件传递 MeasureSpec 设定自己的尺寸的。这里重点讲一下 ViewGroup,为了获得合理的宽高尺寸,ViewGroup 在计算自己尺寸的时候,必须预先知道所有子 View 的尺寸,举个例子,用一个常用的流式布局 FlowLayout 来讲解一下如何合理的设定自己的尺寸。


先分析一下 FLowLayout 流式布局(从左到右)的特点:FLowLayout 将所有子 View 从左往右依次放置,如果当前行,放不开的就换行。从流失布局的特点来看,在确定 FLowLayout 尺寸的时候,我们需要知道下列信息,


  • 父容器传递给 FlowLayout 的 MeasureSpec 推荐的大小(超出了,显示不出来,又没意义)

  • FlowLayout 中所有子 View 的宽度与宽度:计算宽度跟高度的时候需要用的到。

  • 综合 MeasureSpec 跟自身需求,得出合理的尺寸


首先看父容器传递给 FlowLayout 的 MeasureSpec,对开发者而言,它可见于 onMeasure 函数,是通过 onMeasure 的参数传递进来的,它的意义上面的已经说过了,现在来看,怎么用比较合理?其实 ViewGroup.java 源码中也提供了比较简洁的方法,有两个比较常用的 measureChildren 跟 resolveSize,在之前的分析中我们知道 measureChildren 会调用 getChildMeasureSpec 为子 View 创建 MeasureSpec,并通过 measureChild 测量每个子 View 的尺寸。那么 resolveSize 呢,看下面源码,resolveSize(int size, int measureSpec)的两个输入参数,第一个参数:size,是 View 自身希望获取的尺寸,第二参数:measureSpec,其实父控件传递给 View,推荐 View 获取的尺寸,resolveSize 就是综合考量两个参数,最后给一个建议的尺寸:


public static int resolveSize(int size, int measureSpec) {


return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;


}


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);


}

评论

发布
暂无评论
Android自定义View:MeasureSpec的真正意义与View大小控制