Android自定义View基础之MeasureSpec详解

自定义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()等调用过程和相关方法。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中自定义View可以通过以下步骤实现: 1. 创建一个继承自View或其子类的类,例如继承自View的MyView类。 2. 在该类的构造函数中初始化一些属性,例如: ``` public MyView(Context context) { super(context); //初始化一些属性 mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.FILL); } ``` 3. 重写该类的onMeasure方法,以便在该View被加入到View hierarchy时能正确地测量它的大小,例如: ``` @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //测量宽度 int width = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { mWidth = width; } else { mWidth = DEFAULT_WIDTH; } //测量高度 int height = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (heightMode == MeasureSpec.EXACTLY) { mHeight = height; } else { mHeight = DEFAULT_HEIGHT; } setMeasuredDimension(mWidth, mHeight); } ``` 4. 重写该类的onDraw方法,以便在该View被绘制时能够绘制一些内容,例如: ``` @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制一个矩形 canvas.drawRect(0, 0, mWidth, mHeight, mPaint); } ``` 5. 在布局文件中使用该自定义View,例如: ``` <com.example.myview.MyView android:id="@+id/myView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> ``` 注意:以上仅为自定义View的基本步骤,具体实现方式可能根据需求有所不同。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值