Android6.0源码分析之View(二)--measure
来源:互联网 发布:linux发行版排行 2016 编辑:程序博客网 时间:2024/05/17 08:40
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拼在一块儿。
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方法来保存数据
- Android6.0源码分析之View(二)--measure
- Android6.0源码分析之View(一)
- Android6.0源码分析之Settings(二)
- View的绘制流程分析之二 -- measure
- Android View的工作流程总结分析(二)-Measure
- 【自定义View】02--measure源码详尽分析
- 从源码分析View的measure过程
- Android6.0源码分析之Settings(一)
- Android6.0源码分析之录音功能(一)
- Android6.0源码分析之蓝牙
- Android6.0源码分析之蓝牙
- 【Android】View绘制过程分析之measure
- 基于Android6.0的Activity加载View源码分析
- Android View measure (二) 自定义UI控件measure相关
- android6.0源码分析之AMS服务源码分析
- android6.0源码分析之AMS服务源码分析
- Android6.0源码解读之View点击事件分发机制
- Android View measure (一) 流程分析
- NativeStackBlur 高斯模糊
- 暴力破解无线密码最详细教程2017
- Android RxJava 使用
- 自定义异常处理(样例)
- FilteredTextBox
- Android6.0源码分析之View(二)--measure
- Android 中Xml里面的id重名问题
- Codeforces Round #387(Div. 2)D. Winter Is Coming【思维+dp】
- 写一个名为 &total 的子程序,返回一列数字的和
- rk3288之释放打印log的串口
- SSL和SSH有什么区别
- Error:(95, 74) 警告: 最后一个参数使用了不准确的变量类型的 varargs 方法的非 varargs 调用; 对于 varargs 调用, 应使用 Class<?> 对于非 varar
- 比特币的储藏
- C++