Android6.0源码分析之View(二)--measure

Android6.0源码分析之View(一)

紧接着来学习view的measure,(注,开始写博客之后,很明显我的学习效率高多了,研究了俩星期硬是没有研究view的measure,接下来终于可以来好好研究研究了)

先总体分析一下view的measure,发现关于view的measure研究主要涉及到两个方法和一个类

两个方法是

  • onMeasure
  • measure

一个类是,MeasureSpec。

接下里就是有针对性的研究

转载请注明出处,

http://blog.csdn.net/zrf1335348191/article/details/53739658

Chapter One,MeasureSpec分析

MeasureSpec属于View的静态公共的内部类,可以通过View.MeasureSpec调用。

测量规范其实故名思义可以知道就是父view规定以什么样的方式进行测量子view,简单介绍一下MeasureSpec:

1>,测量规范中是父view对子view的布局要求,每一个MeasureSpec对象只包含一种测量规范,要么是父view对子view的宽度测量要求,要么是高度测量要求。测量规范是一个int型数值,一个测量规范由size和mode共同组成,准确来说是int型数值的前两位是mode,后30位是size的值

2>,测量规范的mode模式有三种

  • UNSPECIFIED(未指定的):父view对子view的大小不做限制,子view想要多大就多大
  • EXACTLY(准确的):父view已经对子view的大小有个明确的规定值,所以无论子view想要多大必须使用父view对子view的这个值
  • AT_MOST(至多):父view给子view的大小规定一个上限值,子view想要多大就多大但不能超过这个上限值

3>,makeMeasureSpec方法介绍

public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {//判断是否是17版本或者更低的版本
                return size + mode;
            } else {//
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

方法说明如下:

在API17或者更低的版本,size和mode这两个参数顺序无关,也就是说方法传入的可以是(size,mode)也可以是(mode,size),measureSpec返回的是两个值的和,在两个参数相加时有可能会有溢出值,溢出值可以影响所获取的measureSpec对象,这就是一个bug,现在的Relativelayout就存在这样一个bug。所以在开发APP的时候最好是适配API17以上的版本

方法分析:

i>,传入的参数:

  • size:所规定的view的size
  • mode:所规定的view的测量标准

ii>,sUseBrokenMakeMeasureSpeck:判断target的版本是否是API17以下。

//获取到当前app的target的版本
 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

            // Older apps may need this compatibility hack for measurement.
            sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1;

由这个代码顺便可以掌握一个知识---------如何获取应用的targetversion:context.getApplicationInfo().targetSdkVersion

JELLY_BEAN_MR1是在android.os.Build.java中定义的静态final常量,为17。

总得来说,方法的目的是通过一组size和mode的值来获取一个measureSpec对象,只是因为API版本的不同构建measureSpec的方式不同,apI17版本是个分界线,低版本的是两值相加,高版本的是通过与或操作把size和mode拼在一块儿。

4>,getMode--借助measureSpec对象获取到测量模式,getSize---获取size

 public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

获取size和mode的方法都是借助measureSpec对象获取值,在get方法中涉及到一个静态常量MODE_MASK

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;

MODE_MASK的值的获取是16进制的0x3无符号左移30位等到,如下所示

一个int型数据有32位,MODE_MASK左移30位后的结果是第32位和第31位为1,其余为0. 所以getMode返回的是measureSpec & MODE_MASK--即measureSpec的高两位(32位和31位) getSize返回的是measureSpec & ^MODE_MASK(注:MODE_MASK取反)----即measureSpec的第一位到第30位,即如下图

5>,adjust方法

static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {//父view对子view的size不做什么要求
// 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);
}

该方法传入了两个参数,一个是测量规范measureSpec,一个是size的增量--delta。如果父view未给子view明确指定一个size或者size范围,即mode为UNSCIFIED,此时子view的大小与measurespec中的size有关,否则,需要考虑增量。 基本上到这里MeasureSpec类中的重要方法和变量已经介绍完毕,总结一下就是,测量规范中规定了父view对子view的要求----mode+size,并且根据mode的不同,对size进行一定的调整。 接下来对onMeasure进行一个分析,onMeasure属于生命周期方法,先来看一下onMeasure方法的实现与介绍。 Chapter Two,onMeasure方法简介 1>,onMeasure方法用于测量view以及其内容的宽高,得到一组宽和高的值measurewidth/heigh,在调用measure方法时会调用(measure方法属于view的public方法),View的子类应该覆写onMeasure方法来提供一组准确有效的测量值。 2>,约定:在覆写onMeasure方法时必须调用setMeasuredDimension方法来存储所测量的宽高值,如果存储失败会触发measure抛出 的illegalStateException异常,调用父类的onMesure方法也可以避免这个异常。(笔者注:也就是说要么调用父类的onMesure方法,要么自己手动在子view的onMesure方法中调用setMearsuredDimension方法,否则会抛出异常) 3>,如果在测量规范中没有规定更大的值那么基类中的测量值默认是background的大小,建议view的子类覆写onMeasure方法来进行更好的测量、 4>,如果子类覆写了该方法,那么测量view大小的任务就交给子类了,所测量的宽高不能小于view本身提供了一组view的最小值,可以通过getSuggestedMinimumHeight()/width()获取,获取到的是一组默认的没有padding的一组wrap_content的宽高 5>,方法参数介绍 widthMeasureSpec,宽度测量规范:父view给子view规定的水平方向上的测量规范,测量规范会包含一个mode和一个size heightMeasureSpec,高度测量规范: onMeasure的方法的代码很少,贴出来继续分析分析。

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

看似只是简简单单的调用了setMeasuredDimension方法,但所传入的参数也是经过了一个计算,其实总共是调用了三个方法, 抽丝剥茧,先来看看最先调用的getSuggestedMininumWidth()(高度与宽度获取类似,所以以宽度为例) 《1》,getSuggestedMinimumWidth()

protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

方法功能: 该方法返回的是所建议的view应该使用的最小宽度---在view的最小宽度和背景的最小宽度两个值中取最大值返回 方法参数: mMinWidth:view的最小宽度 getMinimumWidth():drawable的最小宽度 也就是说,该方法会返回一个系统所建议的view应该使用的最小宽度,这个最小宽度由view和view的drawable共同决定 《2》,getDefaultSize(int size, int measureSpec)

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        
        //获取到测量规范对象中的size和mode
       int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:

           //如果mode属于UNSPECIFIED即父view未对子view的大小做任何要求,则将默认的size返回给view
          result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
          
          //父view对子view的测量进行了限制,则所返回的值就是测量规范中size的值,
           //即父veiw给子view规定的值
          result = specSize;
            break;
        }
        return result;
    }

方法功能: 返回一个默认的size:宽度或者是高度。如果测量规范中没有对子view的大小进行限制的话,子view的大小使用该返回值。也有可能返回更大的值。 方法参数: size:默认的view的size,可以通过getDefaultSize获取 measureSpec:子view对父view所限定的测量规范 方法分析如上注释。 方法总结: 也就是说,该方法返回了view的默认大小的值,这个值跟父view对子view是否进行了限制有关, 如果父view对子view没进行限制,则返回所建议的view的大小, 若进行了限制,则返回测量规范对象中的size。 《3》,setMeasuredDimension(int measuredWidth, int measuredHeight)

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);//判断该view布局模式是否有一些特殊的边界
        if (optical != isLayoutModeOptical(mParent)) {//判断view和该view的父view的布局模式情况,如果两者不同步,则进行子view的size大小的修改
//即有两种情况会进入到该if条件,一是子view有特殊的光学边界,而父view没有,此时optical为true
//,一种是父view有一个特殊的光学边界,而子view没有,
//此时optical为false
           Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

           
           measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

方法介绍: 在调用onMeasure方法时必须调用该方法,来保存view所测量的宽和高,如果调用失败则会触发异常。 方法参数: measuredWidth:view的测量的宽, measuredHeight: 方法中调用了

 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

setMeasuredDimensionRaw的作用就是保存所测量的宽和高的值,并且设置标志位 还有一个optical的boolean值,获取到的是layoutmode,

 /** Return true if this ViewGroup is laying out using optical bounds. */
    boolean isLayoutModeOptical() {
        return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
    }
 /**
     * This constant is a {@link #setLayoutMode(int) layoutMode}.
     * Optical bounds describe where a widget appears to be. They sit inside the clip
     * bounds which need to cover a larger area to allow other effects,
     * such as shadows and glows, to be drawn.
     */
    public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;

如果view有一个光学的边界比如阴影,发光等等,则optical的值为true。 在两种情况下,measuredWidth受这个影响 第一种,父view有这个特殊的边界,子view没有即optical为false 此时,子view所能够布局的空间应该减去父view的边界 ???????待验证 所传入的measured的size不包含opticalbounds,如果父view有 第二种,子view有这个特殊的边界,父view没有即optical为true 方法中有一个Insets对象,这个对象有四个参数:

 private Insets(int left, int top, int right, int bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }

指的是矩形的四条边即view的边界的偏移量,由四条边向中心靠拢为正值 Chapter Three,measure方法

 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
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // 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
            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做了个什么事儿呢? 可以看出,对于measuredHeight和measuredWidth有一个缓存的map,measure方法进行一个判断,到底是需要从该缓存区读出measuredHeight和measuredWidth的值还是调用onMeasure方法重新进行测量,对于onMeasure方法的调用,有个版本界限,19版本以下是不论缓存区有没有存储值都会强制调用onMeasure,19版本以上不会。 如果没调用onMeasure方法,就会调用setMeasuredDimensionRaw方法来存储height和width(这个包括size和state) 如果调用了就会根据flags来判断是否调用了setMeasuredDimension方法,如果没调用就会抛出异常 其实归根结底还是为了调用setMeasuredDimensionRaw方法来保存数据

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券