一般计时类的自定义 View 都要考虑一个问题,那就是绘制是需要时间的,虽然很短,但是随着时间的推移,误差就会越来越大,我之前写过自定义 View 实现钟表功能的博客,当时是通过每秒钟获取一次系统时间,然后根据系统时间重新绘制来保证不会产生误差的,如果要实现秒表功能,这个方法明显不行,应为秒表是自己计时的,和系统时间没有关系,为了防止产生误差,我们可以每秒钟都执行一次动画,一秒钟过完之后,无论动画有没有执行完,强制将时间推进到一秒之后,由于一秒钟之内产生的误差非常小,几乎无法察觉,这样就能有效的防止随着时间的推移而产生较大的误差了,接下来就直接贴上自定义秒表的代码
attrs;
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="StopWatchView">
<!--半径-->
<attr name="radius" format="dimension"/>
<!--外圆颜色-->
<attr name="circle_color" format="color"/>
<!--主刻度颜色-->
<attr name="main_scale_color" format="color"/>
<!--其他刻度颜色-->
<attr name="other_scale_color" format="color"/>
<!--最小刻度颜色-->
<attr name="third_scale_color" format="color"/>
<!--秒针针颜色-->
<attr name="big_pointer_color" format="color"/>
<!--毫秒针颜色-->
<attr name="small_pointer_color" format="color"/>
<!--小圆盘颜色-->
<attr name="small_dial_color" format="color"/>
</declare-styleable>
</resources>
StopWatchView:
public class StopWatchView extends View {
private int mainScaleColor; //主刻度颜色
private int otherScaleColor; //其他刻度颜色
private int thirdScaleColor; //最小刻度颜色
private int bigPointerColor; //秒针针颜色
private int smallPointerColor; //毫秒针颜色
private int smallDialColor; //小圆盘颜色
private int circleColor; //外圆颜色
private Paint paint; //画笔
private Bitmap background; //背景图片
private float viewRadius; //半径大小
private float oneUnit; //一个单位长度
private float bigPointerDegree; //秒针转过的角度
private float smallPointerDegree; //毫秒针转过的角度
private int count; //动画执行次数
private ValueAnimator animator; //执行的属性动画
public StopWatchView(Context context) {
super(context, null);
}
public StopWatchView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
initPaint();
initAnimator();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//去长度和宽度的最小值作为尺寸,保证view是一个正方形
int specSize = Math.min(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
setMeasuredDimension(specSize, specSize);
if (viewRadius == - 1) //如果未设置半径,把半径设为view的长度的一半
viewRadius = specSize / 2;
oneUnit = (float) (viewRadius / 453.0); //设置一个单位的大小
}
@Override
protected void onDraw(Canvas canvas) {
canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
drawBackground(canvas);
drawBigScale(canvas);
// drawNumber(canvas);
canvas.save();
canvas.translate(0, - oneUnit * 134);
drawSmallDial(canvas);
drawSmallPointer(canvas);
canvas.restore();
canvas.save();
drawBigPointer(canvas);
canvas.restore();
}
//初始化控件
private void initView(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.StopWatchView);
viewRadius = ta.getDimension(R.styleable.StopWatchView_radius, -1);
circleColor = ta.getColor(R.styleable.StopWatchView_circle_color, Color.BLACK);
mainScaleColor = ta.getColor(R.styleable.StopWatchView_main_scale_color, Color.BLACK);
otherScaleColor = ta.getColor(R.styleable.StopWatchView_other_scale_color, Color.BLACK);
thirdScaleColor = ta.getColor(R.styleable.StopWatchView_third_scale_color, Color.BLACK);
bigPointerColor = ta.getColor(R.styleable.StopWatchView_big_pointer_color, Color.BLACK);
smallDialColor = ta.getColor(R.styleable.StopWatchView_small_dial_color, Color.BLACK);
smallPointerColor = ta.getColor(R.styleable.StopWatchView_small_pointer_color, Color.BLACK);
ta.recycle();
background = BitmapFactory.decodeResource(getResources(), R.drawable.stop_watch_background);
bigPointerDegree = 0;
smallPointerDegree = 0;
count = 0;
}
//初始化画笔
private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
}
//初始化属性动画
private void initAnimator() {
animator = ValueAnimator.ofFloat(0, 360);//每秒钟毫秒针转一圈,正好是360度
animator.setDuration(1000);
animator.setInterpolator(new LinearInterpolator());//设置线性执行动画
animator.setRepeatCount(ValueAnimator.INFINITE);//设置无限循环
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//设置动画监听,获取当前动画的值,重新计算指针转过的角度,然后重绘
smallPointerDegree = (float) animation.getAnimatedValue();
bigPointerDegree = 6 * count + (float) animation.getAnimatedValue() / 60;
invalidate();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d("TAG", "onAnimationStart");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d("TAG", "onAnimationEnd");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d("TAG", "onAnimationCancel");
}
@Override
public void onAnimationRepeat(Animator animation) {
//重复执行的时候强制把时间推进一秒,如果达到60秒的话,说明秒针已经转过一圈了,
//把动画执行次数重置为0
smallPointerDegree = 0;
bigPointerDegree = 6 * count;
count++;
if (count >= 60) {
count = 0;
}
}
});
}
// 计算宽度
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
viewRadius = - 1;
} else {
//若长度为wrap_content,如果未设置半径,把长度设为200,否则长度为半径的两倍
if (specMode == MeasureSpec.AT_MOST) {
if (viewRadius == - 1) {
result = 200;
} else {
result = (int) (viewRadius * 2);
}
}
}
return result;
}
// 计算高度
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
viewRadius = - 1;
} else {
if (specMode == MeasureSpec.AT_MOST) {
if (viewRadius == - 1) {
result = 200;
} else {
result = (int) (viewRadius * 2);
}
}
}
return result;
}
//画背景
private void drawBackground(Canvas canvas) {
int radiusInt = (int) viewRadius;
Rect rect = new Rect(- radiusInt, - radiusInt, radiusInt, radiusInt);
canvas.drawBitmap(background, null, rect, null);
paint.setStrokeWidth(4 * oneUnit);
paint.setColor(circleColor);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(0, 0, oneUnit * 261, paint);
}
//画大刻度
private void drawBigScale(Canvas canvas) {
paint.setColor(mainScaleColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(oneUnit * 3);
for (int i = 0; i < 12; i++) {
canvas.drawLine(0, - oneUnit * 254, 0, - oneUnit * 238, paint);
canvas.rotate(30);
}
paint.setColor(otherScaleColor);
paint.setStrokeWidth(oneUnit * 2);
for (int i = 0; i < 60; i++) {
if (i % 5 != 0) {
canvas.drawLine(0, - oneUnit * 254, 0, - oneUnit * 244, paint);
}
canvas.rotate(6);
}
paint.setColor(thirdScaleColor);
for (int i = 0; i < 300; i++) {
if (i % 5 != 0) {
canvas.drawLine(0, - oneUnit * 254, 0, - oneUnit * 248, paint);
}
canvas.rotate((float) 1.2);
}
}
/* private void drawNumber(Canvas canvas) {
paint.setColor(mainScaleColor);
paint.setTextSize(oneUnit * 20);
for (int i = 0; i < 12; i++) {
canvas.drawText(5 * i + "", (float) (Math.sin(i * Math.PI / 6) * oneUnit * 220), (float) (- Math.cos(i * Math.PI / 6) * oneUnit * 220), paint);
// canvas.rotate(30);
}
}*/
//画小圆盘和圆盘上的刻度
private void drawSmallDial(Canvas canvas) {
paint.setColor(smallDialColor);
paint.setStrokeWidth(oneUnit * 2);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(0, 0, oneUnit * 52, paint);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(oneUnit);
for (int i = 0; i < 24; i++) {
canvas.drawLine(0, - oneUnit * 51, 0, - oneUnit * 46, paint);
canvas.rotate(15);
}
}
//画秒针
private void drawBigPointer(Canvas canvas) {
paint.setColor(bigPointerColor);
paint.setStrokeWidth(0);
canvas.drawCircle(0, 0, (float) (oneUnit * 9.5), paint);
Path path = new Path();
path.moveTo((float) (- oneUnit * 4.5), oneUnit * 34);
path.lineTo((float) (oneUnit * 4.5), oneUnit * 34);
path.lineTo((float) (oneUnit * 2.5), oneUnit * - 254);
path.lineTo((float) (- oneUnit * 2.5), oneUnit * - 254);
path.close();
canvas.rotate(bigPointerDegree);
canvas.drawPath(path, paint);
}
//画毫秒针
private void drawSmallPointer(Canvas canvas) {
paint.setColor(smallPointerColor);
canvas.drawCircle(0, 0, oneUnit * 5, paint);
Path path = new Path();
path.moveTo((float) (- oneUnit * 2.5), 0);
path.lineTo((float) (oneUnit * 2.5), 0);
path.lineTo(0, - oneUnit * 46);
path.close();
canvas.rotate(smallPointerDegree);
canvas.drawPath(path, paint);
}
//开始计时
public void start() {
if (animator != null && !animator.isStarted())
animator.start();
}
//暂停计时
public long pause() {
if (animator != null && animator.isRunning()) {
long playTime = animator.getCurrentPlayTime();
animator.cancel();
return playTime;
}
return 0;
}
//暂停后重新计时
public void restart(long playTime) {
if (animator != null && !animator.isRunning()) {
animator.setCurrentPlayTime(playTime);
animator.start();
}
}
//重置秒表状态
public void clean() {
if (animator != null && !animator.isStarted() && !animator.isRunning()) {
animator.end();
count = 0;
bigPointerDegree = 0;
smallPointerDegree = 0;
}
}
}
注释写得很清楚了,这里不再过多解释