Android自定义View基础之MeasureSpec详解

来源:互联网 发布:上海雅居乐万豪java 编辑:程序博客网 时间:2024/06/01 08:38

自定义view,首先通过measure layout draw三部曲。measure主要负责测量view的大小和模式,layout主要负责view的显示位置,draw来将view绘制出来从而显示在界面上。

首当其冲的就是measure方法,在这个方法里所做的工作就是测量,那么首先就先介绍一下一个类MeasureSpec,明确这个后,才能更方便我们自定义view后面的进行。

MeasureSpec含义

首先,先来看一下官方文档对于MeasureSpec的描述。

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height.A MeasureSpec is comprised of a size and a mode.

通过以上信息的描述,我们可以得到以下三点:

MeasureSpec封装了父布局传递给子布局的要求
MeasureSpec代表了需要的宽高值
MeasureSpec是由大小和模式组成的

MeasureSpec一般译为测量规格,是一个32位的int值。其中高2位代表规格模式,低30位代表测量大小。

  • 获取测量模式
int mode = MeasureSpec.getMode(measureSpec);//内部实现为/**  * Extracts the mode from the supplied measure specification.  *  * @param measureSpec the measure specification to extract the mode from  * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},  *  {@link android.view.View.MeasureSpec#AT_MOST} or  *  {@link android.view.View.MeasureSpec#EXACTLY}  */ public static int getMode(int measureSpec) {    return (measureSpec & MODE_MASK); }
  • 获取测量大小
int size = MeasureSpec.getSize(measureSpec);//内部实现为 /**  * Extracts the size from the supplied measure specification.  *  * @param measureSpec the measure specification to extract the size from  * @return the size in pixels defined in the supplied measure specification  */  public static int getSize(int measureSpec) {       return (measureSpec & ~MODE_MASK);  }
  • 自定义生成新的measureSpec
int measureSpec = MeasureSpec.makeMeasureSpec(size, mode);//内部实现为/**  * Creates a measure specification based on the supplied size and mode.  *  * The mode must always be one of the following:  * <ul>  *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>  *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>  *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>  * </ul>  *  * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's  * implementation was such that the order of arguments did not matter  * and overflow in either value could impact the resulting MeasureSpec.  * {@link android.widget.RelativeLayout} was affected by this bug.  * Apps targeting API levels greater than 17 will get the fixed, more strict  * behavior.</p>  *  * @param size the size of the measure specification  * @param mode the mode of the measure specification  * @return the measure specification based on size and mode  */ public static int makeMeasureSpec(int size, int mode) {     if (sUseBrokenMakeMeasureSpec) {         return size + mode;     } else {         return (size & ~MODE_MASK) | (mode & MODE_MASK);     } }

MeasureSpec模式

measureSpec一共有三种模式,分别为UNSPECIFIED EXACTLY AT_MOST,下面分别做一下介绍

MeasureSpec.EXACTLY

官方描述为:

/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/

在此模式下,父容器已经检测出子view所需要的精确大小,这个时候,view的测量大小就是通过getSize得到的数值。

MeasureSpec.AT_MOST

官方描述为:

/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/

在此模式下,父容器未能检测出子view的大小,但指定了一个最大大小spec size,子view的大小不能超过此值。

MeasureSpec.UNSPECIFIED

官方描述为:

/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/

在此模式下,父容器不对子view的大小做限制,一般用于系统内部,或者ListView ScrollView等滑动控件。

MeasureSpec形成过程

首先,测量子view实在viewgroup中完成的,那么就从viewgroup的measureChildWithMargins方法开始,看一下measurespec的形成过程。

measureChildWithMargins

源码如下:

/**     * Ask one of the children of this view to measure itself, taking into     * account both the MeasureSpec requirements for this view and its padding     * and margins. The child must have MarginLayoutParams The heavy lifting is     * done in getChildMeasureSpec.     *     * @param child The child to measure 测量目标子view     * @param parentWidthMeasureSpec The width requirements for this view 父容器宽的measureSpec     * @param widthUsed Extra space that has been used up by the parent horizontally (possibly by other children of the parent) 父容器在横向空间上已经占据的大小     * @param parentHeightMeasureSpec The height requirements for this view 父容器高的measureSpec     * @param heightUsed Extra space that has been used up by the parent vertically (possibly by other children of the parent) 父容器在纵向空间上已经占据的大小     */    protected void measureChildWithMargins(View child,            int parentWidthMeasureSpec, int widthUsed,            int parentHeightMeasureSpec, int heightUsed) {// 获取子view的layoutparams        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();// 获取子view宽的measureSpec        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec , mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);// 获取子view高的measureSpec        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec , mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);// 将测量出来的值传递给子view        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

如上所示,此方法主要有四步,如下:

  • 得到子view的布局参数
  • 计算子view宽的measureSpec
  • 计算子view高的measureSpec
  • 子view进行measure测量

getChildMeasureSpec

在measureChildWithMargins方法中,计算子view高宽的measureSpec值时,都调用了getChildMeasureSpec方法,下面,看一下该方法都做了哪些事情

/** * Does the hard part of measureChildren: figuring out the MeasureSpec to pass to a particular child. This method figures out the right MeasureSpec for one dimension (height or width) of one child view. * * The goal is to combine information from our MeasureSpec with the LayoutParams of the child to get the best possible results. For example, if the this view knows its size (because its MeasureSpec has a mode of EXACTLY), and the child has indicated in its LayoutParams that it wants to be the same size as the parent, the parent should ask the child to layout given an exact size. * * @param spec The requirements for this view 父容器的measureSpec值 * @param padding The padding of this view for the current dimension and margins, if applicable 当前已经占据的空间 * @param childDimension How big the child wants to be in the current dimension 子view声明的大小 * @return a MeasureSpec integer for the child 返回子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        // 父容器已经明确字view的大小        case MeasureSpec.EXACTLY:        //childDimension指子view在声明宽高是所指定的具体大小,比如100dp,200dp等,LayoutParams.MATCH_PARENT=-1和LayoutParams.WRAP_CONTENT=-2             if (childDimension >= 0) {//子view宽高有具体值,size就是自己声明的值,mode为精确的                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {//子view宽高没有具体值,但声明为占满父容器的空间,因此它的size就是可用最大空间,mode也为精确的                // Child wants to be our size. So be it.                resultSize = size;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//子view宽高声明为包裹内容,那么其size为父容器在横或纵空间上可用的最大空间,mode为最大不超过此值                // 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        // 父容器未检测出子view的大小,但是为其size声明了一个最大值        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) {//同上,填满父容器,但不能超过父容器的可用空间,mode为at_most模式                // 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) {//同上,子view为包裹内容,最大值不能超过父容器的可用空间,mode为at_most模式                // 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        // 一般应用于系统内部或者listview scrollview等滑动控件,不做详细分析        case MeasureSpec.UNSPECIFIED:            if (childDimension >= 0) {//同上,子view有具体值,so...                // 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 = 0;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = 0;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        //生产子view的measureSpec        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

如上,巴拉巴拉一通,就确定了子view的size和mode,同时,就确定了传递给子view的measureSpec值。

从上面的调用过程,我们也可以看出,子view的measureSpec是由父容器的measureSpec和子view本身的layoutParams值共同决定的。
整理上述measureSpec的创建过程,可以得到下图:

这里写图片描述

measureChild与measureChildWithMargins区别

上面介绍了measureChildWithMargins调用getChildMeasureSpec的过程,其实还有一个方法也调用了getChildMeasureSpec,那就是measureChild。

  • 共同点
    • 两者都是测量子view的大小
    • 两者在调用getChildMeasureSpec时都需要计算父容器已占空间,即mPaddingLeft + mPaddingRight
  • 不同点
    • measureChildWithMargins出了计算父容器已占空间,还会计算子view左右两侧margin值,因为这块区域也是不允许摆放子view的

好了,至此为止,measureSpec含义 模式以及创建规则就基本说完了,下一篇开始,开始介绍onMeasure()等调用过程和相关方法。

0 0