目前在阅读Android群英传的第三章自定义控件架构和自定义控件,准备写下自己的学习笔记和一些心得,增强自己.如果文章有错误的地方,请各位朋友指出.文章的内容有些参考书中的东西和自己的理解.
**在此,感谢作者 **
1.Android自定义控件架构
-
对于书中讲的控件架构我是这样理解的,无论是View还是ViewGroup都是继承View的.
View的架构应该是这样的ViewGroup里面可以包含View和ViewGroup.包含的ViewGroup也可以拥有View和ViewGroup. -
对于Activity和View的关系的理解,Activity中包含一个Window对象,书中说道是一般由PhoneWindow来实现.PhoneWindow中加载了一个DecorView,而DecorView包含TitleView和ContentView,而这个ContentView就是我们常在Activity中调用的OnCreate()方法中setContentView(R.xx.xx)加载XML布局文件来实现的.这些并没有在代码中进行实践.
2. Android自定义控件的测量(View的测量)
- 对于View的测量,就像你去画画,你总要知道你要在黑板上哪一块地方去画,画多大,然后在进行绘制.
而在Android中,其实是一样的,你的View总要知道在那个布局中哪个地方显示,显示多大.而系统是需要对View进行测量的.测量是通过View的onMeasure()方法来对View进行测量的.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
在Android 中提供了MeasureSpec类来帮助我们对View进行测量.测量的模式分为三种:
EXACTLY(精准模式)
该模式为精准模式,当你为View的layout_width或者layout_height指定固定的值或者为match_parent的时候为精准模式.
代码示例:
- AT_MOST(最大值模式)
该模式为最大值模式,当控件的layout_width指定为wrap_content的时候,View会随着子View的大小而改变自身大小,比如一个LinearLayout的宽指定为wrap_content的时候,它的宽度就会随着子View的宽度大小而改变.或者根据View中的内容而改变自身大小,比如EditText的宽度设置为wrap_content的时候,文字越多,它的款的就会越长.但是一般都不要超过父View的宽度.
- UNSPECIFIED
该模式不是很理解.不指定View的测量模式,想多大就多大,通常情况用于自定义控件.
View类的onMeasure()方法默认只支持EXACTLY(精准模式),但是我们自定义的控件肯定要支持
layout_width=wrap_content,那么我们就要自己去写代码来支持AT_MOST模式.就需要在View中重写onMeasure()方法来实现.
为什么说View会默认支持EXACTLY,而灭有支持AT_MOST,我们先通过重写的onMearsure()方法中查看父类View的onMearsure()方法,在super.onMearsure()中是这样实现的.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
查看父类View的代码我们明白最终调用 setMeasuredDimension( )
来江测量的宽高设置进去,而完成测量工作.所以在我们测量完宽高后也需要调用setMeasuredDimension()
来完成测量后的设置.那么,我们去查看getSuggestedMinimumWidth()
方法的实现.
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
此方法只返回了一个int值,并没有看见对模式的修改,那么回来我们接着看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;
}
在这里我们发现,该方法初始化了一个result的int类型变量.并且通过MeasureSpec类的getmode()来获取测量模式和getSize()来获取测量后的值,然后进行switch.在这里我们发现有3个case,分别为MeasureSpec.UNSPECIFIED,直接将传过来的size直接返回.当case为MeasureSpec.AT_MOST的时候,我们发现什么都没有事实现,所以说View默认是不支持AT_MOST模式的.当case为MeasureSpec.EXACTLY的时候,是将测量后的值返回.
- 实现自定义控件的
onMearsure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidthOrHeight(widthMeasureSpec),measureWidthOrHeight(heightMeasureSpec));
}
private int measureWidthOrHeight(int measureSpec) {
int result=0;
//获取当前View的测量模式
int mode = MeasureSpec.getMode(measureSpec);
//精准模式获取当前Viwe测量后的值,如果是最大值模式,会获取父View的大小.
int size = MeasureSpec.getSize(measureSpec);
if (mode==MeasureSpec.EXACTLY){
//当测量模式为精准模式,返回设定的值
result=size;
}else{
//设置为WrapContent的默认大小
result=50;
if (mode==MeasureSpec.AT_MOST){
//当模式为最大值的时候,默认大小和父类View的大小进行对比,返回最小的值
result= Math.min(result,size);
}
}
return result;
}
在
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法中,我将
super.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
删除,因为父类的方法中,直接进行了测量并将测量的值进行设置.在重写的onMearsure()
方法中,我们需要对宽高重新测量并且支持AT_MOST
模式,当然跟之前父类里面的实现原理是一样的,都是要调用setMeasuredDimension(int wdith,int height)
设置测量后的值,只不过我们需要对宽高进行重新测量,重新测量的实现由measureWidthOrHeight(int measureSpec)
实现.我们需要获取控件对应的测量模式,返回相对应的测量值.