View绘制详解(三),扒一扒View的测量过程

所有东西都是难者不会,会者不难,Android开发中有很多小伙伴觉得自定义View和事件分发或者Binder机制等是难点,其实不然,如果静下心来花点时间把这几个技术点都研究一遍,你会发现其实这些东西都很简单。OK,废话不多说,今天我们就来看看View的测量。View的测量纷繁复杂,不过如果能够做到提纲挈领,其实也不难。那么今天,我们就来扒一扒View的测量。本文主要涉及如下知识点:

1.View的测量

2.在父容器中对View进行测量

3.LinearLayout测量举例

4.最根上容器测量

如果小伙伴们还没看过我之前关于View绘制的文章的话,请先移步这里,这两篇文章有助于你理解本篇文章:

1.View绘制详解,从LayoutInflater谈起

2.View绘制详解(二),从setContentView谈起

OK,那我们开始今天的话题吧。

1.View的测量

关于View的测量我其实在之前的一篇文章中已经提到过了(Android自定义View之ProgressBar出场记 ),在我们自定义View的时候,除了一个onDraw方法可以重写之外,还有一个onMeasure方法也可以重写,这个onMeasure方法就是用来确定一个View的宽和高的,onMeasure方法的方法头如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 大家看到,onMeasure方法有两个参数,第一个参数叫做widthMeasureSpec、第二个参数叫做heightMeasureSpec,在开发中我们把这两个参数称作测量规格。测量规格是一个32位的int型数据,其中高2位表示测量模式,低30位表示测量值,测量模式一共分为三种:

1.EXACTLY:精确模式,对应我们在布局文件中设置宽高时给一个具体值或者match_parent2.AT_MOST:最大值模式:对应设置宽高时给一个wrap_content3.UNSPECIFIED:这种测量模式多用在ScrollView中,或者系统内部调用

在实际开发过程中,我们一般通过MeasureSpec.getMode()方法来从测量规格中获取测量模式,然后通过MeasureSpec.getSize()方法来从测量规格中获取测量值。但是小伙伴们注意,这个时候获取到的测量值实际上是系统建议的测量值,并不是控件最终显示的大小,在onMeasure方法中我们可以根据自己的需求再对这些值做进一步的修正,修正完之后再调用setMeasuredDimension()方法,调用完该方法之后View才算是有了MeasureWidth和MeasureHeight了。OK,基于以上的表述,我们在自定义View中onMeasure方法的典型写法可以是如下样子:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//获取宽的测量模式int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取宽的测量值int widthSize = MeasureSpec.getSize(widthMeasureSpec);//获取高的测量模式int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获取高的测量值int heightSize = MeasureSpec.getSize(heightMeasureSpec);switch (widthMode) {case MeasureSpec.EXACTLY:break;case MeasureSpec.AT_MOST:case MeasureSpec.UNSPECIFIED://如果宽为wrap_content,则给定一个默认值widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());break;}switch (heightMode) {case MeasureSpec.EXACTLY:break;case MeasureSpec.AT_MOST:case MeasureSpec.UNSPECIFIED:heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());break;}widthSize = heightSize = Math.min(widthSize, heightSize);//设置测量结果setMeasuredDimension(widthSize, heightSize); }2.在父容器中对View进行测量

看完了View的测量之后,很多小伙伴可能都会有疑问了,那么View的onMeasure方法到底是在哪里调用的呢(小伙伴们注意,我这里的View既包括普通控件,也包括容器)?其实就是在它的父容器中调用。那么这里就要我们来到ViewGroup中探究一番了,首先,在ViewGroup中,系统给我们提供了三个方法用来测量ViewGroup中子控件的大小,如下:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

第一个方法是measureChildren,Children是child的复数形式,很明显,这个是测量所有子控件的,这个方法中是一个for循环,遍历了容器中所有的子控件进行测量,第二个方法measureChild则是测量单个子控件,最后一个measureChildWidthMargins也是测量单个子控件,不过在测量的时候加入margin而已。OK,那我们这里就以measureChildWithMargins为例,来看看父容器到底是怎么样来测量子控件的:

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

首先在第四行获取到每一个子View的LayoutParams,然后在第6行通过getChildMeasureSpec方法获取一个childWidthMeasureSpec,高度的测量规格获取方式和宽度测量规格的获取方式一致,所以这里我就以宽度的测量规格获取方式为例,我们来看看系统是如何测来子控件的:

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 uscase 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 uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size… so be itresultSize = 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 becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size… let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size… find out how big it should// beresultSize = 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 beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}//noinspection ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);}流转的时光,都成为命途中美丽的点缀,

View绘制详解(三),扒一扒View的测量过程

相关文章:

你感兴趣的文章:

标签云: