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方法来保存数据