● MeasureSpec
这是View类得一个静态内部类,可以理解为测量规格,和子View的测量有关,子View的测量还与LayoutParam类有关,这个类将父View的布局需要传递给子View,每一个MeasureSpec代表父View对子View在width或者height上的布局规格。
MeasureSpec通过将SpecMode和SpecSize打包为一个int值,可以节省存储空间。SpecMode使用高两位表示,有三个取值,UNSPECIFIED,父View不限制子View,要多大给多大;EXACTLY,父View已经测出了子View所需大小,这个时候View的最终大小由SpecSize决定,对应LayoutParam的match_parent和具体数值两种模式,AT_MOST,父View指定一个可用大小SpecSize,子View不能超出这个大小,具体大小由要看不同view实现,对应LayoutParam的wrap_content。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
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);
}
}
@UnsupportedAppUsage
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
在View测量的时候,系统会将LayoutParam结合父View的MeasureSpec计算出自己的MeasureSpec,比较特别的是顶级View,其Measure由自身的LayoutParam和窗口尺寸决定。MeasureSpec一旦确定,onMeasure就可以测量View的宽和高了;
● View的工作流程
● measure
对于单个view,measure方法调用onMeasure,然后调用setMeasuredDimension存储调用getDefaultSize计算出的宽和高。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED://通常系统调用
result = size;
break;
case MeasureSpec.AT_MOST:计算的宽和高等于measureSpec中的SpecSize;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
对于ViewGroup,出了调用自己的measure过程以外,还需要测量子View,measureChildern->对于每一个childView调用measureChild,计算出mwasureSpec,然后调用child的measure,这样将上一层的measure过程传递到了下一层;
实际上在onCreate,onStart和onResume中均无法获得某个view的宽高信息,这是以为measure的测量过程和Activity的生命周期是不同步的,无法保证在某个生命周期时view已经测量完毕。可以重写onWindowFocusChanged来获得view的宽和高,这个方法会在Activity获得焦点和失去焦点时被调用;view.post(rRunnable action)将一个Runnable对象投递到消息队列尾,当执行到时,view已经测量完毕;ViewTreeObserver的很多回调借口也可以完成。
● layout过程用于确定view在父布局中的位置。layout->setFrame确定view四个顶点位置,接着调用onLayout方法,这个方法需要具体的ViewGroup实现,在这个方法中调用setChildFrame设置子View的四个顶点坐标,setChildFram中会调用子view的setFrame方法,这样就将layout过程传递到了下一层。
○ draw过程的作用是将view绘制到屏幕上,首先绘制背景background.draw(canvas),然后绘制自己,onDraw(),绘制children,diapatchDraw(),绘制装饰onDrawScrollBars。
网友评论