at_most、unspecified、exactly

以下是我个人对at_most、unspecified、exactly模的个人理解,是首先给出这几个字段的简短描述,这几个字段是测量View的时候用到的,这些字段用来描述父view传给子view的高度或者宽度的意义
就是告诉子view这些高度和宽度你该如何参考,首先,得知道这个模式,是父view向子view传递的。
at_most: 这个子view 最大不能超过这个值。
unspecified:大小不确定,还得你自己用尺子给自己量一下,父view尽量给你所需的长度。
exactly:这个 子view 你就是这么大了。

正常人都知道一个基本view的绘制基本需要3个过程:measure -> layout -> draw
参考父view参考的长宽来具体测量(或者确认)自己的长宽以及子view长宽 -> 确定自己在父view上的坐标位置以及子view在自己区域的坐标位置 -> 绘制到屏幕上

我们来看view的测量阶段,单纯看一下view的onMeasure 方法,它有两个参数:widthMeasureSpec、heightMeasureSpec,这两个参数是父view给子View参考用的。
这个值肯定不是乱给的,这个包含了长度(高宽)与模式(at_most、unspecified、exactly)
当前 view 测量完成之后,需要调用 setMeasuredDimension(width,height) 设置,相信大家都是了解这个过程的。我注意看了一下 View.java 中对于这个方法调用是这样的:
setMeasuredDimsion(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec))
于是我查看了 getDefaultSize 的源码如下
 /**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    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;
    }
从getDefaultSize 的源码中我们可以得出这些个结论,默认View的测量实现中,除了unspecified模式之外,其他模式都是用了父 view 传递过来的长度,换句话说,系统或者源码编写者至少是这么用的。
我这有个问题,父view为啥要给子view传递一个参考长度呢? 我自己测量自己不行吗?对于这个问题,我举个例子:
同志们都知道,我们平时用的控件,比如TextView,在xml的布局中必然有一个父布局,必须有个布局容器来装它。这里有两个属性android:width,android:height 是必须写的,否则就会出错。这里设定的值
就是父view穿给TextView的参考值。现在想一下代码中动态创建一个TextView的过程的代码
LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
TextView text_view = new TextView(this);
ll.addView(text_view);
text_view = new TextView(this);
text_view.setText("计划好了再娶吧");

text_view.setBackgroundColor(Color.RED);
ll.addView(text_view);
如果你实验过了,我为什么要说动态添加view,因为我想说一下这个LayoutParams 参数,我们可以主动为我们的text_view 设置,也可以使用默认的,所谓默认就是你在向父布局添加的过程中(addView过程)
父布局发现你没有设置LayoutParams,会主动为你设置一个layoutParams。对LinearLayout来讲如果是VERTICAL模式,那么该布局默认为子view生成的LayoutParams 的宽度就是match_parent
不管是默认还是自己创建设置,最终父布局会拿到LayoutParams里面的各种参数,其中就包括了对子view的宽高的参考值。

这里我们拿LinearLayout的测量,在LinearLayout 也会参考它的父布局所给的参考。因为本身是个布局容器,它会依次测量它的每一个孩子,大概会调用这个方法
protected void measureChildWithMargins(View child,
                       int parentWidthMeasureSpec,
                                    int widthUsed,
                      int parentHeightMeasureSpec,
                                   int heightUsed)
方法的源码如下:
 /**
     * 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
     * @param parentWidthMeasureSpec The width requirements for this view
     * @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
     * @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) {
        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);
    }
通过源码我们可以看出这个方法所干的事情:
1、得到子view的 MarginLayoutParams
2、确定子view的长宽的参考值getChildMeasureSpec(...)
3、调用子view的measure 并传入参考值,进行递归测量child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
看了一下getChildMeasureSpec(..)的源码,稍微有点长,但是我们必须知道子view的参考值是怎么来的,虽然知道是android:width 或 android:height 中设置,但怎么转换的我们需要看一下
  /**
     * 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 = 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;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
注释写的很明白:这是测量子view的关键,目的就是为了得到要给这个子view传递MeasurepSpec的大小(测量尺寸 + 规格), 是通过我们的MeasureSpec 和 LayoutParams参数来得到的。
注意:这里的this view指的是不是测量的子view 而是当前view
方法签名:public static int getChildMeasureSpec(int spec, int padding, int childDimension)

spec: 父容器提供当前View的长度(尺寸+规格)。
padding: 该view 的padding 和 被其他子view占据的部分等。
childDimension: 我们通过LayoutParams 设定的值(match_parent, wrap_content, 固定值) 这里仅仅是尺寸。
这样我们就有了两个参考点:我的MeasureSpec,子view的LayoutParams。

计算剩余空间: int size = Math.max(0, specSize - padding)
大概就是通过父控件的specMode(父控件的父控件传过来的)和LayoutParams 里面的参数,来确定具体给这个子view如何传参考值,测量完之后就得到了(mode + size)。在测量完成每一个子view的测量高度之后,
父容器根据自身的padding和相应的子view的宽高之和得到自身的测量结果。
先写这么多,也不知道理解的对不对,请多指教。
有一个问题,为甚对于一个TextView,我们调用TextView.measure(0, 0) 会知道TextView长宽。
当调用measure就表示自身测量(TextView的自身测量很复杂),但是一定会参考一下父控件传过来的值,并且只有mode是exactly 或者 at_most的时候回参考。否则就是自己的测量结果,因为传递了0那么,得到的mode就是 unspecified的,因此就是测量自身的大小。也就是最小
宽度和高度。

转载于:https://www.cnblogs.com/QiaoJun-Fighting/p/6106169.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值