天天看点

Android 中MeasureSpec的创建规则

概述

在Android中,View的onMeasure()方法用来对控件进行测量,确定控件的宽高。该方法的两个参数widthMeasureSpec和heightMeasureSpec由父View计算后传入子view的measure()方法,再由子view的measure()方法传入onMeasure()方法,本文将介绍MeasureSpec的创建规则

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
           

LayoutParams

在开始源码分析前,我们要先介绍下LayoutParams,因为子view的MeasureSpec的创建需要用到子view的LayoutParams,LayoutParams有如下三种类型

  • FILL_PARENT/MATCH_PARENT:填满父View
  • WRAP_CONTENT:包裹内容
  • 确定值:确定的值

源码分析

首先我们要先找到父View中调用子view的measure()方法的入口

首先先看View类的onMeasure方法如下,由于View是所有控件的基类,这里只是一个默认实现

我们应该看得是ViewGroup类型的类的onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
           

查看源码后发现ViewGroup.java类中并未重写onMeasure()方法,且ViewGroup类是个抽象类,所以我们应该把目光投向ViewGroup的子类,比如AbsoluteLayout、LinearLayout、FrameLayout等

我们选其中一个看看,查看AbsoluteLayout的onMeasure()方法,可以知道如下的调用栈

可以看到measureChildren()和measureChild()是定义在ViewGroup中的,在子类中可以访问

AbsoluteLayout.onMeasure()->ViewGroup.measureChildren()->ViewGroup.measureChild()

接下来直接看measureChild()的源码

由下面的源码及其注释可以看到,子view的MeasureSpec是交由getChildMeasureSpec()方法来计算,终于是让我们找到了

/**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    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);
    }
           

接下来我们就看看getChildMeasureSpec()方法的源码,也是本文的重点内容

该方法的功能就是通过父view的MeasureSpec和子View的LayoutParams来计算出子View的MeasureSpec

父View的MeasureSpec有MeasureSpec.EXACTLY、MeasureSpec.AT_MOST和MeasureSpec.UNSPECIFIED三种类型,而子view的LayoutParams也有MATCH_PARENT、WRAP_CONTENT或者确定值三种类型,3*3得出9种情况,如下代码所示

/**
     * 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
     * @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
     * @return a MeasureSpec integer for the child
     */
    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 = 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;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
           

总结

子view的MeasureSpec创建规则如下表所示

Android 中MeasureSpec的创建规则