简单理解View的onMeasure过程
View的绘制需要经过Measure、Layout、Draw这三个流程。很多朋友在自定义View的时候,特别是对Measure过程不能十分理解,这里结合Android的一些源码和资料来简单说明。
首先来看一下View的measure方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//...
onMeasure(widthMeasureSpec, heightMeasureSpec);
//...
}
注意到measure方法是final修饰的,也就是说这个方法是不允许重写的,而且方法内部主要是调用了onMeasure方法,测量工作交给onMeasure来做,所以我们在自定义View时总是重写其onMeasure方法。注意到传入的两个int类型的参数,widthMeasureSpec和heightMeasureSpec,这两个参数代表什么呢:
其高2位表示MODE,也就是测量的模式;低30位代表SIZE,测量的值。可以通过MeasureSpec.getMode()和MeasureSpec.getSize()得到它们。
平时我们给View定义的match_parent或具体的值,其模式就是MeasureSpec.EXACTLY的,而wrap_content的话就是MeasureSpec.AT_MOST,其他为未指定。
重点看onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure是怎么处理measure方法传入的两个参数的,再看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:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
所以我们看到,onMeasure是根据其模式来确定值的,如果MODE是未指定的,则result返回默认值,如果MODE是AT_MOST或EXACTLY,则返回传入的measureSpec中获取的size确定值。
到setMeasuredDimension这一步,就最终把测量的值确定下来,整个过程就已经测量完了。这里要注意一点就是,在onMeasure方法中调用setMeasuredDimension方法时,必须是在自己已重写的onMeasure方法中调用这个方法,否则会抛出一个异常。这就是普通View的测量过程,那么我们有疑问,又是谁调用了measure方法呢,换句话说,谁来确定普通View的widthMeasureSpec和heightMeasureSpec是多少?
一般子View的实际宽高是由其父视图和本身决定的,父视图是有义务测量其每一个子View,也就是说子View的measure方法其实是其上层的ViewGroup调用的(最顶层是从ViewRootImpl的performTraversals方法开始),而ViewGroup在测量子View的时候,会采纳子View提供的值,这个值就是从我们平时给View设置的ViewGroup.Layoutparams里获取的。我们还是通过源码来理解,以下就是看ViewGoup这个类是如何测量其孩子的:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);//为其每一个孩子进行测量
}
}
}
measureChild方法又是怎么做的:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();//拿到了子View的Layoutparams
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//调用子View的measure方法
}
最后getChildMeasureSpec方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
//...
break;
case MeasureSpec.UNSPECIFIED:
//...
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
以传入的spec的模式为MeasureSpec.EXACTLY一项来说,其他两项类推:
如果子View设置的LayoutParams大于0(也就是具体的值,因为MATCH_PARENT用-1表示,WRAP_CONTENT用-2表示),就把子View的size设置为这个具体的值,mode设置为EXACTLY;如果子View设置的是LayoutParams.MATCH_PARENT,子View的size就是父View的size减去padding(这个值大于0的情况下;若小于0则设置为0),mode设置为EXACTLY;如果子View设置的是LayoutParams.WRAP_CONTENT,那么size最大为父View的size-padding,mode设置为AT_MOST。
这也就说明了为什么子View的大小是由其父视图和本身决定的。
更多相关文章
- android kotlin webview内H5调用微信支付问题
- Binder通信机制
- Android(安卓)ionic工程中调用webrtc获取视频流
- Android中Activity组件的生命周期
- Android知识点(自己版)
- Android(安卓)NDK之JNI陷阱
- Android网络访问的基本方法
- Android中保存和恢复Fragment状态的最好方法
- Android文件存储--采用SharedPreferences保存用户偏好设置参数和