概述
Android群英传这本书已经入手一段时间了,没有抽出时间来好好看,从今天开始,准备仔细研读一下,并且把每章值得留意的地方以笔记的形式记录下来,方便以后复习,这是第一篇,因为前两章分别讲的是Android的体系结构与相关的开发工具,都是一些概念性的东西,没有什么难度,大家知道就行了。好了,开始我们的笔记吧!
笔记
View的测量
1.为什么说View默认的onMeasure方法只支持EXACTLY模式?
解答这个问题,我们先看看View中onMeasure方法的定义,
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
measure方法接收两个参数,这两个参数分别封装了宽度的大小和模式以及高度的大小和模式,是由父类计算后,传递下来的,那么谁调用了这个方法呢?
View的测量是从ViewRootImpl的performTraversals函数开始的,在这个方法里面依次会调用View的measure,layout,draw方法。我们看看ViewRootImpe中调用View的measure方法的地方。
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
那么这个mView是什么呢?通过查找源码,我们发现有如下的代码
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
....
}
mView是从setView这个方法传过来的,那么setView中的这个View是什么呢?是DecorView,这里我就不详细分析了,有兴趣的同学可以看我的另一篇文章,这里会有介绍,Activity启动流程以及View的绘制流程,调用DecorView的measure方法,然后传入两个参数,我们看这两个参数是如何定义的。
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
都调用了getRootMeasureSpec方法,传入两个参数,第一个参数为Window的尺寸,很显然这个尺寸是固定的,第二个参数是代表Window的布局参数,
private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
这个方法根据传进来的rootDimension返回一个封装了模式和大小的变量,很显然我们传进来的rootDimension是MATCH_PARENT的,因为根View的大小是Window的大小,然后我们回到调用measure方法的地方,
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
这里的两个参数的之就是getRootMeasureSpec方法的返回值,经过上面的分析,我们知道这两个参数的中的模式都是EXACTLY的
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
}
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
....
然后这两个参数会传递给onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//获取默认的大小
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方法的模式是EXACTLY的。
2.自定义View的时候为什要重写measure方法,不重写有什么后果?
当我们制定宽高为wrap_content时,如果不重写onMeasure方法,那么系统就不知道该使用默认多大的尺寸,因此,默认它会填充整个父布局,下面我会通过代码给大家验证。因所以重写onMeasure方法的目的,就是为了能够给view一个wrap_content属性下的默认大小。
下面通过一个例子讲解一下,我们自定一个View
public class MyView extends View {
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//保存测量结果,此方法必须调用
setMeasuredDimension(getMeasuredWidth(widthMeasureSpec),getMeasuredHeight(heightMeasureSpec));
}
public int getMeasuredWidth(int widthMeasureSpec){
int size = 0;
//获取尺寸
int specSize = MeasureSpec.getSize(widthMeasureSpec);
//获取模式
int mode = MeasureSpec.getMode(widthMeasureSpec);
switch (mode){
case MeasureSpec.EXACTLY://在布局中写死的数值,或者match_parent
size = specSize;
break;
case MeasureSpec.UNSPECIFIED:
size = 200;
break;
case MeasureSpec.AT_MOST://布局中属性为wrap_content
//当属性为wrap_content时,specSize为屏幕大小
System.out.println("==========="+specSize);//720
size = 200;
size = Math.min(size,specSize);
break;
}
return size;
}
public int getMeasuredHeight(int heightMeasureSpec){
int size = 0;
int specSize = MeasureSpec.getSize(heightMeasureSpec);
int mode = MeasureSpec.getMode(heightMeasureSpec);
switch (mode){
case MeasureSpec.EXACTLY:
size = specSize;
break;
case MeasureSpec.UNSPECIFIED:
size = 200;
break;
case MeasureSpec.AT_MOST:
size = 200;
size = Math.min(size,specSize);
break;
}
return size;
}
}
我们重写了onMeasure方法,最后通过setMeasuredDimension将测量后的结果保存了起来,这里的重点在getMeasuredWidth方法,这个方法的作用是,根据父类传过来的规格计算子View的规格,我们看看是如何实现的,
首先,分别取出模式和尺寸,然后我们根据模式去判断,如果是精确的,说明布局文件里面写的是固定尺寸或者match_parent,我们就将取出的值设置给最终结果,如果是UNSPECIFIED,我们给它设置200,如果是AT_MOST,说明布局文件里面写的是wrap_content,这时候我们要取specsize和200的最小值,这里有一点需要注意,当模式为AT_MOST的时候,spesize1的值是和当前屏幕尺寸一样的,所以,在这里我们需要取一个比较小的值
上面过程分析完毕之后,我们看看运行结果,首先设定模式为精确的
<com.example.administrator.myapplication.MyView
android:layout_width="200px"
android:layout_height="200px"
android:background="#ff0000"
/>
可以看到,显示除了长宽为200px的正方形
接下来,我们设定模式为AT_MOST,
<com.example.administrator.myapplication.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000"
/>
按照上面的代码,我们给了一个默认的大小200,然后从specsize之间取最小值,很明显,结果大家能猜到,和上面一样,
最后我们看看不重写onMeasure方法的结果,
此时,整个屏幕都被占领了,也验证了我们之前的结论。
自定义View
View中常用的回调:
//XML加载组件后调用
@Override
protected void onFinishInflate() {
super.onFinishInflate();
}
//组件大小改变时
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
实现自定义的三种方式
- 对现有控件进行扩展
- 通过组合来实现新的控件
重写View来实现全新的控件
重新onTouchEvent方法时要注意它的返回值,一般情况下我们要返回true来处理事件,如果返回false或者super.onTouchEvent(event),则只能响应ACTION_DOWN事件,后面的MOVE,UP都不能响应。
事件传递
事件传递是从上向下,事件处理是从下向上,事件处理都是onTouEvent方法,默认都是返回false。
本文深入解析了Android中View的测量机制,重点介绍了View默认onMeasure方法仅支持EXACTLY模式的原因,以及自定义View时为何需要重写measure方法及其重要性。
467

被折叠的 条评论
为什么被折叠?



