从源码角度分析一下OnMeasure

来源:互联网 发布:赛嘉电动牙刷 知乎 编辑:程序博客网 时间:2024/05/20 09:48

上一篇文章以一个自定义view的形式初步认识了OnMeasure

文章链接

这篇文章我就从源码的角度深入的探究一下
看下谷歌的官方文档,看不懂英文没关系,一句一句翻译一下:
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:

MeasureSpec 封装着由父控件传递到子控件的布局要求。每一个MeasureSpec就代表着一个宽或者一个高的布局要求。一个MeasureSpec就是一个尺寸和模式的组合。下面就是三种可能出现的模式:

UNSPECIFIED
The parent has not imposed any constraint on the child. It can be whatever size it wants.

父控件没有给子控件强加任何的约束。子控件想要多大就要多大

EXACTLY
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.

父控件给了子控件一个非常明确的尺寸。这个尺寸就是子控件的大小

AT_MOST
The child can be as large as it wants up to the specified size.

子控件可以在指定的尺寸内尽可能的大。

MeasureSpecs are implemented as ints to reduce object allocation. This class is provided to pack and unpack the (size, mode) tuple into the int.

为了减小对象分配控件,MeasureSpecs用一个32位的int数据表示(其中高2位代表SpecMode即某种测量模式,低30位为SpecSize代表在该模式下的规格大小)

下面是 MeasureSpecs 类的一些信息:

这里写图片描述

那么OnMeasure是什么时候掉用的呢,一个view肯定是放在view group中的,我们常用的各种布局RelativeLayout LinearLayout…..等等都是继承自viewGroup,我们如果自定义viewGroup我们就要在它的onMeasure里面计算子view的大小,类似下面

@Override   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {       super.onMeasure(widthMeasureSpec, heightMeasureSpec);       // 获得它的父容器为它设置的测量模式和大小       int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);       int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);       int modeWidth = MeasureSpec.getMode(widthMeasureSpec);       int modeHeight = MeasureSpec.getMode(heightMeasureSpec);       //一般我xml里面设置布局的时候都是知道它的大小,所以模式一般都是MeasureSpec.EXACTLY       int e = MeasureSpec.EXACTLY;       int a = MeasureSpec.AT_MOST;       int u = MeasureSpec.UNSPECIFIED;       //遍历每个子元素       for (int i = 0, childCount = getChildCount(); i < childCount; i++) {           View childView = getChildAt(i);           //测量每一个子view的宽和高           measureChild(childView, widthMeasureSpec, heightMeasureSpec);       }       setMeasuredDimension(sizeWidth,sizeHeight);   }

方法里面的 measureChild 这个方法就是计算子view布局的,我们一步步的看下去

/**     * 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 的方法,三个参数第一个父控件MeasureSpec,第二个父控件的padding值,第三个设置的子 view的大小,具体什么样呢,跳进去继续看

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

这个方法注释写的非常清楚(一定要看注释,代码可以略过),虽说是英文注释,但是都很简单,关于MeasureSpec.UNSPECIFIED,实际中我没有用过,但是还是那句话,这个自定义view的时候用的不多,用的时候再去研究吧。

获取完字view的MeasureSpec然后回头看掉用child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
这个方法才真正的调用到了子 view的OnMeasure();

然后回头看我的上篇文章,是不是又有了多一点的收获呢。

下一篇简单的说一下OnLayout(),篇幅不回太长,view的大小我们都测量完了,让它放哪不就放那吗

冰冻三尺,非一日之寒~我们一起努力~

0 0