自定义View/ViewGroup的一般步骤:
1) 自定义属性;
2) 选择和设置构造方法;
3) 重写onMeasure()方法;
4) 重写onDraw()方法;
5) 重写onLayout()方法;
6) 重写其他事件的方法(滑动监听等)。
自定义属性
在values目录下面创建自定义属性的XML,比如attrs.xml,文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color" />
</declare-styleable>
</resources>
这里声明了一个自定义属性的集合叫“CircleView”,在这个集合里面可以有很多自定义属性,这里只定义一个格式为“circle_color”的属性,,格式是color,当然还有其他自定义属性的格式,比如reference是指资源id,dimenson是指尺寸等,常用的format格式类型有:
1) string:字符串类型;
2) integer:整数类型;
3) float:浮点型;
4) dimension:尺寸,后面必须跟dp、dip、px、sp等单位;
5) Boolean:布尔值;
6) reference:引用类型,传入的是某一资源的ID,必须以“@”符号开头;
7) color:颜色,必须是“#”符号开头;
8) fraction:百分比,必须是“%”符号结尾;
9) enum:枚举类型
选择和设置构造方法
自定义View有三个构造方法,它们的作用是不同的。
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
第一个是new 一个对象的时候调用。
第二个是在XML文件声明的时候调用。
系统默认只会调用前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的。
在第三个构造函数中解析自定义属性的值并做相应处理,这里我们解析circle_color这个属性的值,并初始化画笔的颜色,代码如下:
private int mColor = Color.WHITE;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
a.recycle();
init();
}
private void init() {
mPaint.setColor(mColor);
}
首先加载自定义属性集合CircleView,接着解析CircleView属性集合中的circle_color属性,id为R.styleable.circleView_circle_color,如果使用中没有指定这个属性,那么默认的颜色为红色。在布局文件中使用自定义属性,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#ffffff"
android:orientation="vertical"
tools:context="com.gomez.mycircleview.MainActivity">
<com.gomez.mycircleview.CircleView
android:id="@+id/circleView1"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_margin="20dp"
android:padding="20dp"
app:circle_color="@color/colorPrimary"
android:background="#000000"/>
</LinearLayout>
重写onMeasure方法
这里我们要处理wrap_content的问题,不作处理的话,wrap_content的效果就等于match_parent得效果,这样View就达不到我们预期的效果,代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, 200);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 200);
}
}
上面代码中,我们只需给View指定一个默认的内部宽/高(200dp),并在wrap_content时设置此宽/高。
重写onDraw方法
这里我们画一个圆效果的自定义View,以自己的中心点以宽/高的最小值为直径画一个实心圆,代码如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int radius = Math.min(width, height) / 2;
canvas.drawCircle(width / 2, height / 2, radius, mPaint);
}
因为是直接继承View的自定义View,我们还要处理padding的问题,只要在绘制是把padding考虑进去即可,代码修改如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingBottom - paddingTop;
int radius = Math.min(width, height) / 2;
canvas.drawCircle(getPaddingLeft() + width / 2, getPaddingTop() + height / 2, radius, mPaint);
}