先放张效果图
我们先来说说自定义View的步骤:
1:首先要继承一个View类,这个View可以是View本身,也可以是View的子类(例如:TextView,Button等)。
2:接下来就要定义自定义属性,在构造方法中遍历这些属性,为自定义的属性添加必要的属性和方法。
3:重写onMeasure()方法,根据自己的需要确定控件的大小。
4:重写onDraw()方法绘制自己想要的图形。
接下来根据上面的四个步骤,我们来创建我们的Watch类。
第一步:创建Watch类并继承View,重写两个参数的构造方法。
public class Watch extends View {
public Watch(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
第二步:创建自定义属性attrs.xml文件。
这里我们的自定义属性比较简单,仅仅是设置一下时、分、秒指针的颜色(attrs.xml文件要放在res/values目录下)。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="watch">
<attr name="dialColor" format="color" />
<attr name="scaleColor" format="color" />
<attr name="hourHandColor" format="color"></attr>
<attr name="minuteHandColor" format="color"></attr>
<attr name="secondHandColor" format="color"></attr>
</declare-styleable>
</resources>
在构造方法中遍历我们的自定义属性
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.watch,
0, 0);
try {
dialColor = a.getColor(R.styleable.watch_dialColor, getResources().getColor(R.color.colorAccent));
scaleColor = a.getColor(R.styleable.watch_dialColor, getResources().getColor(R.color.colorAccent));
hourHandColor = a.getColor(R.styleable.watch_hourHandColor, getResources().getColor(R.color.colorAccent));
minuteHandColor = a.getColor(R.styleable.watch_minuteHandColor, getResources().getColor(R.color.colorAccent));
secondHandColor = a.getColor(R.styleable.watch_secondHandColor, getResources().getColor(R.color.colorAccent));
} finally {
a.recycle();
}
在全部获得自定义属性后需要调用TypedArray.recycle()方法,TypedArray对象是共享资源,必须在使用后进行回收。
得到自定义属性后,我们还需要对属性设置一些方法,比如说属性的set()方法,方便我们在代码中直接设置指针颜色。
public void setHourHandColor(int hourHandColor) {
this.hourHandColor = hourHandColor;
hourHandPaint.setColor(hourHandColor);
invalidate(); // 重新绘制
requestLayout(); // 重新布局
}
public void setMinuteHandColor(int minuteHandColor) {
this.minuteHandColor = minuteHandColor;
minuteHandPaint.setColor(minuteHandColor);
invalidate(); // 重新绘制
requestLayout(); // 重新布局
}
public void setSecondHandColor(int secondHandColor) {
this.secondHandColor = secondHandColor;
secondHandPaint.setColor(secondHandColor);
invalidate(); // 重新绘制
requestLayout(); // 重新布局
}
当设置属性后视图需要重新绘制我们应调用invalidate()方法,设置的属性如果影响到原来的布局需要调用requestLayout()重新布局。
第三步:我们需要知道我们的控件的大小。
重写View的onMeasure()方法,根据onMeasure()方法中的参数得到我们控件的测量大小。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int WidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
switch (widthSpecMode) {
case MeasureSpec.UNSPECIFIED:
dialWidth = 20;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
dialWidth = WidthSpecSize;
break;
}
switch (heightSpecMode) {
case MeasureSpec.UNSPECIFIED:
dialHeight = 20;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
dialHeight = heightSpecSize;
break;
}
dialLeft = dialPointWidth;
dialRight = dialWidth - dialPointWidth;
dialTop = dialPointWidth;
dialBottom = dialHeight - dialPointWidth;
circleCenterX = dialWidth / 2;
circleCenterY = dialHeight / 2;
setMeasuredDimension(dialWidth, dialHeight);
}
onMeasure中的两个参数分别对应控件宽度的测量参数和高度的测量参数
MeasureSpec.getMode(widthMeasureSpec)获得具体的测量模式,测量模式一共有三种:
MeasureSpec.UNSPECIFIED 父项没有对子项施加任何约束。 它可以是任何它想要的大小
MeasureSpec.AT_MOST 父母已确定孩子的确切大小。 孩子将被给予那些边界,无论它想要多大MeasureSpec.getSize(widthMeasureSpec)得到测量的具体数值
第四步:绘制我们的图形,整篇文章的重点就在这里了。
重写onDraw()方法,这个方法中有一个Canvas参数,我们要做的就在这个Canvas上进行画图。要想画图肯定需要Paint画笔,为了提高View的绘制效率,画图需要的所有所有对象都应提前创建并实例化。
创建init()方法,实例化各种Paint对象
public void init() {
dialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dialPaint.setStyle(Paint.Style.STROKE);
dialPaint.setColor(dialColor);
dialPaint.setStrokeWidth(dialPointWidth);
scalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scalePaint.setColor(scaleColor);
hourHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
hourHandPaint.setStrokeWidth(hourPointWidth);
hourHandPaint.setColor(hourHandColor);
minuteHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
minuteHandPaint.setStrokeWidth(minutePointWidth);
minuteHandPaint.setColor(minuteHandColor);
secondHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
secondHandPaint.setStrokeWidth(secondPointWidth);
secondHandPaint.setColor(secondHandColor);
}
在构造方法中调用init()方法。
下面是onDraw()方法的具体内容:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
RectF dialRectf = new RectF(dialLeft, dialTop, dialRight, dialBottom);
canvas.drawOval(dialRectf, dialPaint);
canvas.drawPoint(circleCenterX, circleCenterY, dialPaint);
for (int i = 1; i <= 60; i++) {
canvas.rotate(rotateAngle, circleCenterX, circleCenterY); // 旋转画布
if (i % 5 == 0) {
canvas.drawLine(circleCenterX, circleCenterX / 4, circleCenterX, dialTop, scalePaint); // 绘制长刻度线
} else {
canvas.drawLine(circleCenterX, circleCenterX / 8, circleCenterX, dialTop, scalePaint); // 绘制短刻度线
}
}
drawHourHand(hour, canvas); // 绘制时针
drawMinuteHand(minute, canvas); // 绘制分针
drawSecondHand(second, canvas); // 绘制秒针
}
图形绘制好之后感觉好像缺了点什么,现在我们的指针是不会动的,那么怎样才能让指针动起来呢,我的思路是,每隔一秒获取一下系统时间,然后动态设置指针的位置。
public void setTime(int hour, int minute, int second) {
this.hour = hour;
this.minute = minute;
this.second = second;
invalidate();
}
public void getTime() {
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat dateFormat = new SimpleDateFormat("HH");
hour = Integer.parseInt(dateFormat.format(date));
dateFormat = new SimpleDateFormat("mm");
minute = Integer.parseInt(dateFormat.format(date));
dateFormat = new SimpleDateFormat("ss");
second = Integer.parseInt(dateFormat.format(date));
}
创建一个Handler对象,在onSizeChange()方法中获取系统时间,然后循环调用setTime()方法。
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case REFRESH:
getTime();
setTime(hour, minute, second);
sendEmptyMessageDelayed(REFRESH, 1000);
break;
}
}
};
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
getTime();
setTime(hour, minute, second);
handler.sendEmptyMessageDelayed(REFRESH, 1000);
}
下面是Watch类的完整代码(直接拷贝就可以使用):
public class Watch extends View {
private static final String TAG = "watch";
private static final int REFRESH = 1;
private int dialColor;
private int scaleColor;
private Paint dialPaint;
private Paint scalePaint;
private int scaleStartX, scaleStartY, scaleEndX, scaleEndY;
private float dialLeft, dialTop, dialRight, dialBottom;
private int dialWidth, dialHeight;
private int circleCenterX, circleCenterY;
private int dialPointWidth = 5;
private int hourPointWidth = 10;
private int minutePointWidth = 6;
private int secondPointWidth = 3;
private int rotateAngle = 6;
private Paint hourHandPaint, minuteHandPaint, secondHandPaint;
private int hourHandColor, minuteHandColor, secondHandColor;
private int hour, minute, second;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case REFRESH:
getTime();
setTime(hour, minute, second);
sendEmptyMessageDelayed(REFRESH, 1000);
break;
}
}
};
public Watch(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.watch,
0, 0);
try {
dialColor = a.getColor(R.styleable.watch_dialColor, getResources().getColor(R.color.colorAccent));
scaleColor = a.getColor(R.styleable.watch_dialColor, getResources().getColor(R.color.colorAccent));
hourHandColor = a.getColor(R.styleable.watch_hourHandColor, getResources().getColor(R.color.colorAccent));
minuteHandColor = a.getColor(R.styleable.watch_minuteHandColor, getResources().getColor(R.color.colorAccent));
secondHandColor = a.getColor(R.styleable.watch_secondHandColor, getResources().getColor(R.color.colorAccent));
} finally {
a.recycle();
}
init();
}
public void init() {
// 带边缘的表盘
dialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dialPaint.setStyle(Paint.Style.STROKE);
dialPaint.setColor(dialColor);
dialPaint.setStrokeWidth(dialPointWidth);
// 刻度
scalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scalePaint.setColor(scaleColor);
// 时针
hourHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
hourHandPaint.setStrokeWidth(hourPointWidth);
hourHandPaint.setColor(hourHandColor);
// 分针
minuteHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
minuteHandPaint.setStrokeWidth(minutePointWidth);
minuteHandPaint.setColor(minuteHandColor);
// 秒针
secondHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
secondHandPaint.setStrokeWidth(secondPointWidth);
secondHandPaint.setColor(secondHandColor);
}
public void setHourHandColor(int hourHandColor) {
this.hourHandColor = hourHandColor;
hourHandPaint.setColor(hourHandColor);
invalidate(); // 重新绘制
requestLayout(); // 重新布局
}
public void setMinuteHandColor(int minuteHandColor) {
this.minuteHandColor = minuteHandColor;
minuteHandPaint.setColor(minuteHandColor);
invalidate();
requestLayout();
}
public void setSecondHandColor(int secondHandColor) {
this.secondHandColor = secondHandColor;
secondHandPaint.setColor(secondHandColor);
invalidate();
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int WidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
switch (widthSpecMode) {
case MeasureSpec.UNSPECIFIED:// 未确认 测量规范模式:父项没有对子项施加任何约束。 它可以是任何它想要的大小。
dialWidth = 20;
break;
case MeasureSpec.AT_MOST: // 究竟 测量规格模式:父母已确定孩子的确切大小。 孩子将被给予那些边界,无论它想要多大。
case MeasureSpec.EXACTLY: // 最多 测量规格模式:孩子可以大到它想要达到指定的尺寸。
dialWidth = WidthSpecSize;
break;
}
switch (heightSpecMode) {
case MeasureSpec.UNSPECIFIED:
dialHeight = 20;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
dialHeight = heightSpecSize;
break;
}
dialLeft = dialPointWidth;
dialRight = dialWidth - dialPointWidth;
dialTop = dialPointWidth;
dialBottom = dialHeight - dialPointWidth;
circleCenterX = dialWidth / 2;
circleCenterY = dialHeight / 2;
setMeasuredDimension(dialWidth, dialHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
RectF dialRectf = new RectF(dialLeft, dialTop, dialRight, dialBottom);
canvas.drawOval(dialRectf, dialPaint);
canvas.drawPoint(circleCenterX, circleCenterY, dialPaint);
for (int i = 1; i <= 60; i++) {
canvas.rotate(rotateAngle, circleCenterX, circleCenterY); // 旋转画布
if (i % 5 == 0) {
canvas.drawLine(circleCenterX, circleCenterX / 4, circleCenterX, dialTop, scalePaint); // 绘制长刻度线
} else {
canvas.drawLine(circleCenterX, circleCenterX / 8, circleCenterX, dialTop, scalePaint); // 绘制短刻度线
}
}
drawHourHand(hour, canvas); // 绘制时针
drawMinuteHand(minute, canvas); // 绘制分针
drawSecondHand(second, canvas); // 绘制秒针
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
getTime();
setTime(hour, minute, second);
handler.sendEmptyMessageDelayed(REFRESH, 1000);
}
private void drawHourHand(int hour, Canvas canvas) {
int angle1 = minute / 2;
int angle = hour * rotateAngle * 5 + angle1;
canvas.rotate(angle, circleCenterX, circleCenterY);
canvas.drawLine(circleCenterX, circleCenterY, circleCenterX, circleCenterY / 2, hourHandPaint);
canvas.rotate(360 - angle, circleCenterX, circleCenterY); // 将画布旋转到初始位置
}
private void drawMinuteHand(int minute, Canvas canvas) {
int angle = minute * rotateAngle;
canvas.rotate(angle, circleCenterX, circleCenterY);
canvas.drawLine(circleCenterX, circleCenterY, circleCenterX, circleCenterY / 4, minuteHandPaint);
canvas.rotate(360 - angle, circleCenterX, circleCenterY); // 将画布旋转到初始位置
}
private void drawSecondHand(int second, Canvas canvas) {
int angle = second * rotateAngle;
canvas.rotate(angle, circleCenterX, circleCenterY);
canvas.drawLine(circleCenterX, circleCenterY, circleCenterX, dialTop, secondHandPaint);
canvas.rotate(360 - angle, circleCenterX, circleCenterY); // 将画布旋转到初始位置
}
public void setTime(int hour, int minute, int second) {
this.hour = hour;
this.minute = minute;
this.second = second;
invalidate();
}
public void getTime() {
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat dateFormat = new SimpleDateFormat("HH");
hour = Integer.parseInt(dateFormat.format(date));
dateFormat = new SimpleDateFormat("mm");
minute = Integer.parseInt(dateFormat.format(date));
dateFormat = new SimpleDateFormat("ss");
second = Integer.parseInt(dateFormat.format(date));
}
}
自定义View的使用方法:
在xml中使用
<com.jqk.customizewatch.Watch
android:id="@+id/watch"
android:layout_width="200dp"
android:layout_height="200dp"
app:hourHandColor="@color/green"
app:minuteHandColor="@color/red"
app:secondHandColor="@color/blue"
android:layout_centerInParent="true" />
在代码中使用
private Watch watch;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
watch = (Watch) findViewById(R.id.watch);
// watch.setTime(5, 30, 40);
watch.setHourHandColor(getResources().getColor(R.color.green));
watch.setMinuteHandColor(getResources().getColor(R.color.red));
watch.setSecondHandColor(getResources().getColor(R.color.blue));
}