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

以上三个问题可能是自定义ViewGroup最需要考虑的问题,首先先解决第一个问题。

父容器的限制与MeasureSpec


先假定,父容器是300dp*300dp的尺寸,如果子View的布局参数是

android:layout_width=“match_parent”

android:layout_height=“match_parent”

那么按照我们的期望,希望子View的尺寸要是300dp*300dp,如果子View的布局参数是

android:layout_width=“100dp”

android:layout_height=“100dp”

按照我们的期望,希望子View的尺寸要是100dp*100dp,如果子View的布局参数是

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

按照我们的期望,希望子View的尺寸可以按照自己需求的尺寸来确定,但是最好不要超过300dp*300dp。

那么父容器怎么把这些要求告诉子View呢?MeasureSpec其实就是承担这种作用:MeasureSpec是父控件提供给子View的一个参数,作为设定自身大小参考,只是个参考,要多大,还是View自己说了算。先看下MeasureSpec的构成,MeasureSpec由size和mode组成,mode包括三种,UNSPECIFIED、EXACTLY、AT_MOST,size就是配合mode给出的参考尺寸,具体意义如下:

  • UNSPECIFIED(未指定),父控件对子控件不加任何束缚,子元素可以得到任意想要的大小,这种MeasureSpec一般是由父控件自身的特性决定的。比如ScrollView,它的子View可以随意设置大小,无论多高,都能滚动显示,这个时候,size一般就没什么意义。

  • EXACTLY(完全),父控件为子View指定确切大小,希望子View完全按照自己给定尺寸来处理,跟上面的场景1跟2比较相似,这时的MeasureSpec一般是父控件根据自身的MeasureSpec跟子View的布局参数来确定的。一般这种情况下size>0,有个确定值。

  • 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的主要作用:父控件传递给子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 {

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
mg-WTFCdtBb-1715486813260)]

[外链图片转存中…(img-w4QSaJiX-1715486813261)]

[外链图片转存中…(img-o8mlxR62-1715486813263)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值