美文网首页Android view体系、屏幕适配、显示相关
Android进阶之自定义View原理(一)View的Measu

Android进阶之自定义View原理(一)View的Measu

作者: kakaxicm | 来源:发表于2018-04-29 11:33 被阅读30次

    引言

    自定义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原理,在下一篇博客中会详细研究。

    相关文章

      网友评论

        本文标题:Android进阶之自定义View原理(一)View的Measu

        本文链接:https://www.haomeiwen.com/subject/xaholftx.html