类别
自定义View,有三种方式,分别满足不同的需求:
- 扩展Android自带的View控件,在原生控件基础上扩展自己的样式和点击事件;
- 组合式ViewGroup,实现ViewGroup内多个子View的统一逻辑, 比如自定义顶部标题栏;
- 拓展View类,实现完全的自定义样式和点击事件。
基本模式
//拓展对应的View
public class CustomView extend View{
//复写三个构造方法,并提供初始化的init()供初始化Paint类等
public CustomView(Context context){
this(context, null);
}
public CustomView(Context context, AttributeSet attrs){
this(context, null, 0);
}
public CustomView(Context context, AttributeSet attires, int defStyleAttr){
super(context, attrs, defStyleAttr);
init();
}
private void init(){}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(width, height);
}
//一般是ViewGoup复写此方法,实现对子View的布局
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
@Override
protected void onDraw(Canvas canvas) {}
@Override
public boolean onTouchEvent(MotionEvent event) {}
}
理解
onMeasure方法
onMeasure在控件测量时在Measure()中调用,实现自己的宽和高的逻辑后由setMeasuredDimension()方法将宽高设置给控件:
Measure–>onMeasure–>自定义宽高逻辑–>setMeasuredDimension。复写onMeasure时,方法提供此控件的测量模式和经父容器测量后提供给此控件可用控件的宽高值。
我们在设置控件的layout_with属性时,此属性虽然是在控件中设置,但是是提供给此控件所在的父布局使用的。其实可以这么理解,父布局统筹子控件的宽高属性,并将处理后的子控件宽高模拟和可用的使用空间宽高提供给子类。如果控件使用match_parent/精准值(如110dp),则指明此控件宽度根据某一值进行设置,在onMeasure中对应的测量模式是MeasureSpec.EXACTLY, 如果是wrap_content,则对应的测量模式是MeasureSpec.AT_MOST,还有一个MeasureSpec.UNSPECIFIED都说用得少,我也不清楚。
复写此方法没实现宽高逻辑,则默认的效果是,控件可以实现填充父布局或者按指定的值显示宽高,但是无法实现wrap_content效果,这也是我们在onMeasure方法里需要自己实现的部分,其实大概模式如下(以宽为例,高度相同)int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else { //targetSize根据控件要求可以是一个默认的宽度或者是控件自身的宽度 if(widthMode == MeasureSpec.AT_MOST){ width = Min(widthSize, targetSize); } }
onDraw方法
基本的思路是,通过对点击事件和其他事件的响应,通过invalidate()方法来触发控件再次调用onDraw()方法,并且控制方法里变量的值来实现控件绘制的图形对不同事件的变化。