昨夜雨疏风骤。浓睡不消残酒。试问卷帘人,却道“海棠依旧”。知否,知否?应是绿肥红瘦!
一首李清照的《如梦令·昨夜雨疏风骤》送给大家。
本篇博客只是一篇总结,自定义View往往和奇奇怪怪的需求相关。面对不断变化的需求,千万不能“死记硬背”,更重要的是要掌握自定义View的流程。
Android中每个控件都会在界面中占得一块矩形区域,这些控件大致被分为两类,即View控件和ViewGroup控件。ViewGroup控件可以包含多个View控件,并且能管理其包含的View控件。结构如下:
1. 自定义View
1. View的测量
Android系统给我们提供了MeasureSpec类来帮助我们测量View,Measure是一个32位的int值。高2位为测量的模式,低30位为测量的大小。测量的模式可以分为以下三种:
- EXACTLY 精确值模式。例如:
android:layout_width="100dp|match_parent"
- AT_MOST 最大值模式。例如:
android:layout_width="wrap_content"
控件的大小一般随着控件的子空间或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。 - UNSPECIFIED 不指定其大小的测量模式。View想多大就多大,通常在绘制自定义View时使用。
下面是一套通配的代码,可以作为模板使用。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200; //单位px
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
2.在View中通常有以下一些比较重要的回调方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 回调该方法进行测量
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// 确定显示位置
}
@Override
protected void onDraw(Canvas canvas) { // 画布
super.onDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) { // 触摸事件
return super.onTouchEvent(event);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { // 组件大小改变时回调
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onFinishInflate() { // 从XML加载组件后回调
super.onFinishInflate();
}
3. 三种方式实现自定义控件及一般步骤
1. 对已有控件进行拓展 ----> extends TextView
` @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 在回调父类方法前,实现自己的逻辑
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 在回调父类方法后,实现自己的逻辑
}`
2. 通过组合控件来实现新的控件 ----> extends FrameLayout
1. new 元素
2. 设置属性(可以自定义属性)
3. 定义接口(若有需求)
3. 重写View实现全新控件 ----> extends View
1. onMeasure()
2. onLayout()(可选)
3. onDraw()
4. onTouchEvent()...
注:调用invalidate()方法进行重绘可实现动态效果。
自定义属性
1. 在res目录的values目录下创建一个attrs.xml文件。例如:
<resources>
<declare-styleable name="topBar">
<attr name="title" format="string"/>
<attr name="titleTxtSize" format="dimension"/>
</declare-styleable>
</resources>
2. 获取自定义的属性
private void intView(AttributeSet attrs, Context context) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.topBar);
String titleTxt = a.getString(R.styleable.topBar_titleTxt);
float titleTxtSize = a.getDimension(R.styleable.topBar_titleTxtSize, 0);
a.recycle();
}
3. 指定XML命名空间
xmlns:dyk="http://schemas.android.com/apk/res/com.example.testandroid"
dyk:titleTxt="test"
2. 自定义ViewGroup
自定义ViewGroup通常需要重写onMeasure()方法来对子View进行测量,重写onLayout()方法确定子View的位置,重写onToutchEvent()方法增加响应事件。
注:new对象时,调用一个参数的构造方法。未使用自定义属性时,调用两个参数的构造方法,使用自定义属性时调用三个参数的构造方法。