Android进阶之自定义View原理(一)View的Measure过程

引言

自定义View作为Android开发者必须掌握的重点和难点,它是android开发的核心技能之一。网络上有很多介绍它们的文章,但存在一些问题:内容不全、浅尝辄止、无源码分析等等。在接下来的几篇博客当中,我将从View的测量、布局、绘制、触摸事件分发机制以及弹性滚动这几方面入手,从源码层面理解它们各自的实现原理,帮助大家彻底明白自定义View的实现原理,踩一踩坑。(源码为API26,与之前版本可能有些改动,但原理不变。)

(一)View的measure流程

1.理解MeasureSpec

   /**源码分析:理解MeasureSpec
     *  <<========分析(1)========>>
     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. There are three possible
     * modes:
     * <dl>
     * <dt>UNSPECIFIED</dt>
     * <dd>
     * The parent has not imposed any constraint on the child. It can be whatever size
     * it wants.
     * </dd>
     *
     * <dt>EXACTLY</dt>
     * <dd>
     * 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.
     * </dd>
     *
     * <dt>AT_MOST</dt>
     * <dd>
     * The child can be as large as it wants up to the specified size.
     * </dd>
     * </dl>
     *
     * MeasureSpecs are implemented as ints to reduce object allocation. This class
     * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
     */
    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * 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.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        /** <<========分析(2)========>>
         * 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(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
         * will automatically get a size of 0. Older apps expect this.
         *
         * @hide internal use only for compatibility with system widgets and older apps
         */
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**<<========分析(3)========>>
         * 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}
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        /**<<========分析(3)========>>
         * 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);
        }

        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }

分析(1):通过该类的注释,我们知道: MeasureSpec封装父布局传给子布局的布局测量要求,是View测量的依据,它包括两个部分:测量模式和测量大小,它们封装在一个int数中,高两位是测量模式,低30位为大小,二者共同确定子布局的期望大小,为啥弄这么复杂呢?了解C语言嵌入式开发的都知道,通过移位操作将两个信息封装到一个int中,减少对象内存分配。
测量模式有三种:
1> UNSPECIFIED:子布局大小没有任何限制,主要用作系统内部测量,实际开发很少用到;
2> EXACTLY:父布局测出子View所期望的大小就是子View的大小,对应子View的布局参数为match_parent或者具体数值;
3> AT_MOST:父布局给出的期望大小size,子View大小不能超过这个size,对应子View布局参数为wrap_content,该模式下父布局只是限定了子View的大小上限,View的大小计算由自身确定,这里会引申出自定义View的布局参数为wrap_content不起作用的问题,后面会解释这个问题。
分析(2): makeMeasureSpec方法将mode和size封装到一个int里。
分析(3):getMode和getSize方法是通过位操作分别取出mode和size。

2.MeasureSpec的生成

前面说了那么多,这个父布局到底是怎么生成MeasureSpec给子View的呢?它是根据父布局的MeasureSpec以及子View的布局参数(一下简称LP)得到的,具体方法在ViewGroup的getChildMeasureSpec中。

   /**源码分析: getChildMeasureSpec
     *作用: 根据父视图的MeasureSpec & 布局参数LP,计算单个子View的MeasureSpec
     * 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams属性 共同决定
     * 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) {
      /* 注:@param spec 父布局的测量信息,由它的父布局传过来的
       * @param padding :父布局的padding
       * @param childDimension :子View的LP参数
       */
        //父布局的测量模式
        int specMode = MeasureSpec.getMode(spec);
        //父布局的大小
        int specSize = MeasureSpec.getSize(spec);
       //父布局给子View的剩余空间
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        //核心代码:通过父view的MeasureSpec和子view的LayoutParams确定子view的大小
        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY://父布局size确定
            if (childDimension >= 0) {//子布局size确定
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
                //测量结果:EXACTLY+childsize
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//LP为match_parent
                // Child wants to be our size. So be it.
                resultSize = size;//大小为父布局size
                resultMode = MeasureSpec.EXACTLY;
                //测量结果:EXACTLY+parentsize
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//LP为wrap_content
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
                //测量模式:AT_MOST+parentsize
            }
            break;

        // Parent has imposed a maximum size on us
        // 当父布局的模式为AT_MOST时,父view强加给子view一个最大的值。(一般是父view设置为wrap_content) 
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {//子布局大小为具体值
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
                //测量结果:EXACTLY+childsize
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//子view LP=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;
                //测量结果为AT_MOST+parentsize
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//注意:子view LP = wrap_content时候与LP = match_parent的测量结果相同
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
                //测量结果为AT_MOST+parentsize
            }
            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测量结果生成逻辑.png

注意:由图可以看出,自定义View在LP=wrap_content和match_parent,在父布局AT_MOST测量模式下,效果是一样的,因此需要对自定义View在LP=wrap_conten时做特殊处理,指定默认值,这样就解决前面提到的wrap_content失效问题。

3.View的measure()方法

    /**
     * 源码分析:measure()
     * 定义:Measure过程的入口;属于View.java类 & final类型,即子类不能重写此方法
     * 作用:基本测量逻辑的判断
     * <p>
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     * </p>
     *
     * <p>
     * measure方法最终还是会调用onMeasure,正真的测量实现是在onMeasure实现,覆写onMeasure方法必须执行setMeasuredDimension()设置View的测量宽高。
     * The actual measurement work of a view is performed in
     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
     * </p>
     *
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     *
     * @see #onMeasure(int, int)
     */
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
       //布局边界是否可视,开发着模式用,忽略此段代码
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        //避免重复测量,尝试读取缓存,key值有宽高共同决定
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        //首次测量创建缓存
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
        //上面的标志位都是为了确定这个view是否需要重新测量
        if (forceLayout || needsLayout) {//需要重新测量
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();
            //读取缓存
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //如果缓存没有命中,则调用onMeasure重新测量
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                 //读取缓存
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                //最终设置测量结果
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            //这里检测测量标记是否置位,如果没有置位,则表示setMeasuredDimension没有调用,抛异常,所以在自定义View的OnMeasure方法里必须调用setMeasuredDimension方法
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
       //暂存本次测量结果用于重复测量判断
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
        //本次测量结果放入缓存
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

measure的流程:
1>判断是否需要重新测量,如果需要,则走2>,否则走3>;
2>读取缓存:如果缓存命中,则读取缓存值,解析出宽高spec信息作为本次测量结果,然后通过setMeasuredDimensionRaw()设置测量mMeasuredWidth和mMeasuredHeight;如果未命中,则执行onMeasure方法,在onMeasure方法里面需要执行setMeasuredDimension()方法设置测量宽高;
3>保存本次测量结果并存入缓存
4>measure执行的最终测量大小存放在mMeasuredWidth和mMeasuredHeight中。

4.View的onMeasure()方法

/**
  * 分析:onMeasure()
  * 作用:a. 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
  *      b. 存储测量后的View宽 / 高:setMeasuredDimension()
  **/ 
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
   setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    // setMeasuredDimension() :获得View宽/高的测量值 
    // 传入的参数通过getDefaultSize()获得
  }

 /**
  * 分析:setMeasuredDimension()
  * 作用:存储测量后的View宽 / 高
  * 注:该方法即为我们重写onMeasure()所要实现的最终目的
  **/
  protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
        // 将测量后子View的宽 / 高值进行传递
        mMeasuredWidth = measuredWidth;  
        mMeasuredHeight = measuredHeight; 
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;  
  } 
  // 由于setMeasuredDimension()的参数是从getDefaultSize()获得的
// 下面我们继续看getDefaultSize()的介绍

 /**
  * 分析:getDefaultSize()
  * 作用:根据View宽/高的测量规格计算View的宽/高值
  **/
  public static int getDefaultSize(int size, int measureSpec) {  

        // 参数说明:
        // size:提供的默认大小
        // measureSpec:宽/高的测量规格(含模式 & 测量大小)

        // 设置默认大小
       int result = size;
      // 获取宽/高测量规格的模式 & 测量大小
       int specMode = MeasureSpec.getMode(measureSpec);  
       int specSize = MeasureSpec.getSize(measureSpec);  
          
       switch (specMode) {  
           // 模式为UNSPECIFIED时,使用提供的默认大小 = 参数Size
           case MeasureSpec.UNSPECIFIED:  
                result = size;  
                break;  
           // 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值 = measureSpec中的Size
           case MeasureSpec.AT_MOST:  
           case MeasureSpec.EXACTLY:  
                result = specSize;  
                break;  
            }  
         // 返回View的宽/高值
        return result;  
   }

getDefaultSize中的size 参数为getSuggestedMinimumHeight/Width()方法得到:

protected int getSuggestedMinimumHeight() { 
        //如果设置背景,则是背景高和mMinHeight的较大值
       //没设置背景则是mMinHeight
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }

(二)ViewGroup的measure流程

1>(API26)ViewGroup没有覆写measure和onMeasure方法,所以默认情况下measure流程和View的一致
2> ViewGroup不仅要测量自己,还要测量子View,ViewGroup测量子View的方法为measureChildren.
3>和自定义View一样,自定义ViewGroup也需要覆写onMeasure方法,根据子View的测量结果,按照自己的逻辑合并子View的宽高,确定自身的宽高。

/**测量子View
     * Ask all of the children of this view to measure themselves, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * We skip children that are in the GONE state The heavy lifting is done in
     * getChildMeasureSpec.
     *
     * @param widthMeasureSpec The width requirements for this view
     * @param heightMeasureSpec The height requirements for this view
     */
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                //忽略GONE掉的View,INVISIBLE的View仍然测量
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

    /**测量单个Child
     * 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();
        //根据parentMeasureSpec和子ViewLP生成子View的MeasureSpec,具体代码已经在MeasureSpec生成中分析过
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        //子View各自测量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

至此,ViewGroup的measure流程分析完毕,分析完原理,我们最终的目的还是为了学以致用,下面介绍View和ViewGroup的onMeasure的基本套路,这里只介绍流程,具体的实践后面的博客中会根据案例具体实现。

(三)覆写onMeasure方法的基本流程

1.View的onMeasure()方法基本流程:

1>拿到父View传过来的spec,解析出size;
2>对LP=wrap_content的情况,设置默大小;
3>根据自己的逻辑,如等比例宽高、宽高最值限定等等逻辑,结合size,得到最终的resultsize;
4>执行setMeasuredDimension(resultsize)设置测量结果;

//onMeasure伪代码
 private int mDefaultWidth = 200;
    private int mDefaultHeight = 400;
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //Step1:拿到父View期望的大小
        int resultWidth = 0;
        int resultHeight = 0;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //先赋值
        resultWidth = widthSize;
        resultHeight = heightSize;
        //Step2:wrap_content处理
        if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            //在这里实现计算需要wrap_content时需要的宽
            resultWidth = mDefaultWidth;
        }
        if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            //在这里实现计算需要wrap_content时需要的高
            resultHeight = mDefaultHeight;
        }
        //step3:自己定义View的逻辑,如宽高比,大小限制等等
        resultHeight = resultWidth;
        //step4:设置测量结果
        setMeasuredDimension(resultWidth, resultHeight);
    }

上面的代码简单实现了宽高比为1的自定义View,除了第三步,其他三步为固定套路,可以直接用。

2.覆写View的onMeasure()方法基本流程:

1>遍历所有子View,存放它们的大小;
2>根据自己的逻辑,合并子View的大小,得到最终ViewGroup的大小;
3>:设置大小。

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        // 定义存放测量后的View宽/高的变量
        int widthMeasure ;
        int heightMeasure ;
        // Step1. 遍历所有子View(child.measure) or 测量measureChildren()
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        // Step2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值
        
         ... // 核心部分自身实现

        // Step3. 存储测量后View宽/高的值:调用setMeasuredDimension()  
        // 类似单一View的过程,此处不作过多描述
        setMeasuredDimension(widthMeasure,  heightMeasure);  
  }

总结:希望读者读完measure源码,对View/ViewGroup的测量原理有更清晰的认识,有关于measure方法的应用场景,一般在自定义ViewGroup中结合layout使用,关于View的Layout原理,在下一篇博客中会详细研究。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容