前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android6.0源码分析之View(二)--measure

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

作者头像
fanfan
发布2019-05-28 17:37:13
3930
发布2019-05-28 17:37:13
举报
文章被收录于专栏:编程思想之路编程思想之路

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方法介绍

代码语言:javascript
复制
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以下。

代码语言:javascript
复制
//获取到当前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

代码语言:javascript
复制
 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

代码语言:javascript
复制
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方法

代码语言:javascript
复制
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的方法的代码很少,贴出来继续分析分析。

代码语言:javascript
复制
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

看似只是简简单单的调用了setMeasuredDimension方法,但所传入的参数也是经过了一个计算,其实总共是调用了三个方法,

抽丝剥茧,先来看看最先调用的getSuggestedMininumWidth()(高度与宽度获取类似,所以以宽度为例)

《1》,getSuggestedMinimumWidth()

代码语言:javascript
复制
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)

代码语言:javascript
复制
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)

代码语言:javascript
复制
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:

方法中调用了

代码语言:javascript
复制
 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

setMeasuredDimensionRaw的作用就是保存所测量的宽和高的值,并且设置标志位

还有一个optical的boolean值,获取到的是layoutmode,

代码语言:javascript
复制
 /** Return true if this ViewGroup is laying out using optical bounds. */
    boolean isLayoutModeOptical() {
        return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
    }
代码语言:javascript
复制
 /**
     * 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对象,这个对象有四个参数:

代码语言:javascript
复制
 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方法

代码语言:javascript
复制
 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方法来保存数据

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年12月30日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Android6.0源码分析之View(一)
    • http://blog.csdn.net/zrf1335348191/article/details/53739658
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档