一个简单的时钟View(关键点:Canvas的平移与旋转)
程序员文章站
2024-01-31 12:50:25
...
通过Canvas的平移与旋转简化绘图逻辑是一个非常有用的技巧,下面的时钟view就是利用这个方法完成的,省去了使用三角函数计算坐标的麻烦。
package com.example.swt369.simpleclock;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Toast;
import java.util.Calendar;
/**
* Created by swt369 on 2017/8/20.
*/
public class ClockView extends View {
//时钟各个部件的长度
private float mScaleLengthLong;
private float mScaleLengthShort;
private float mTickLengthHour;
private float mTickLengthMinute;
private float mTickLengthSecond;
//view的宽和高
private float mWidth;
private float mHeight;
//时钟半径
private float mRadius;
//绘制各部件时用的Paint
private Paint mPaintScaleLong;
private Paint mPaintScaleShort;
private Paint mPaintOutline;
private Paint mPaintNum;
private Paint mPaintTickHour;
private Paint mPaintTickMinute;
private Paint mPaintTickSecond;
private ViewTreeObserver.OnPreDrawListener onPreDrawListener;
public ClockView(final Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
onPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
//获取view宽高并计算各个部件的长度
@Override
public boolean onPreDraw() {
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
mRadius = Math.min(mWidth,mHeight) / 2 * 0.95f;
mScaleLengthLong = mRadius * 0.1f;
mScaleLengthShort = mRadius * 0.05f;
mTickLengthHour = mRadius * 0.3f;
mTickLengthMinute = mRadius * 0.45f;
mTickLengthSecond = mRadius * 0.6f;
return true;
}
};
getViewTreeObserver().addOnPreDrawListener(onPreDrawListener);
//点击显示具体时间
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Calendar calendar = Calendar.getInstance();
String time = String.format("当前时间:%02d:%02d:%02d",
calendar.get(Calendar.HOUR_OF_DAY),calendar.get(Calendar.MINUTE),calendar.get(Calendar.SECOND));
Toast.makeText(context,time, Toast.LENGTH_SHORT).show();
}
});
//初始化所有Paint对象
initializePaints();
}
@Override
protected void onDraw(Canvas canvas) {
if(mWidth == 0 || mHeight == 0){
return;
}
if(onPreDrawListener != null){
getViewTreeObserver().removeOnPreDrawListener(onPreDrawListener);
onPreDrawListener = null;
}
//绘制时钟
drawClock(canvas);
//一秒后重绘
postInvalidateDelayed(1000);
}
private void drawClock(Canvas canvas) {
//保存原始状态
canvas.save();
//将坐标系原点移到中心,并逆时针旋转90度。完成后x轴朝上。
canvas.translate(mWidth / 2,mHeight / 2);
canvas.rotate(-90);
//画外围轮廓
canvas.drawCircle(0,0,mRadius, mPaintOutline);
//画刻度
for(int i = 0 ; i < 12 ; i++){
String num = String.valueOf(i == 0 ? 12 : i);
if(i % 3 == 0){
canvas.drawLine(mRadius,0,mRadius - mScaleLengthLong,0, mPaintScaleLong);
}else {
canvas.drawLine(mRadius,0,mRadius - mScaleLengthShort,0, mPaintScaleShort);
}
canvas.drawText(num,mRadius - mScaleLengthLong - mPaintNum.measureText(num) * 2,0, mPaintNum);
//顺时针旋转30度
canvas.rotate(30);
}
Calendar calendar = Calendar.getInstance();
//画时针
int hour = calendar.get(Calendar.HOUR);
canvas.save();
canvas.rotate(hour * 30);
canvas.drawLine(0,0,mTickLengthHour,0,mPaintTickHour);
canvas.restore();
//画分针
int minute = calendar.get(Calendar.MINUTE);
canvas.save();
canvas.rotate(minute * 6);
canvas.drawLine(0,0,mTickLengthMinute,0,mPaintTickMinute);
canvas.restore();
//画秒针
int second = calendar.get(Calendar.SECOND);
canvas.save();
canvas.rotate(second * 6);
canvas.drawLine(0,0,mTickLengthSecond,0,mPaintTickSecond);
canvas.restore();
//恢复原始状态
canvas.restore();
}
private void initializePaints(){
mPaintScaleLong = new Paint();
mPaintScaleLong.setAntiAlias(true);
mPaintScaleLong.setStrokeWidth(5);
mPaintScaleShort = new Paint();
mPaintScaleShort.setAntiAlias(true);
mPaintScaleShort.setStrokeWidth(3);
mPaintOutline = new Paint();
mPaintOutline.setStyle(Paint.Style.STROKE);
mPaintOutline.setAntiAlias(true);
mPaintOutline.setStrokeWidth(5);
mPaintNum = new Paint();
mPaintNum.setTextSize(30);
mPaintTickHour = new Paint();
mPaintTickHour.setAntiAlias(true);
mPaintTickHour.setStrokeWidth(6);
mPaintTickMinute = new Paint();
mPaintTickMinute.setAntiAlias(true);
mPaintTickMinute.setStrokeWidth(4);
mPaintTickSecond = new Paint();
mPaintTickSecond.setAntiAlias(true);
mPaintTickSecond.setStrokeWidth(2);
}
}
代码注释已经比较详细了,下面把比较重要的部分再说明一下:
//保存原始状态
canvas.save();
//省略中间部分...
//恢复原始状态
canvas.restore();
在绘图开始时调用canvas.save(),可以保存下未经任何平移、旋转操作的原始画布状态。在所有绘图工作完成后调用canvas.restore(),可以恢复到上一次保存的状态(类似进出栈的感觉)。
//将坐标系原点移到中心,并逆时针旋转90度。完成后x轴朝上。
canvas.translate(mWidth / 2,mHeight / 2);
canvas.rotate(-90);
画布的平移与旋转可能比较抽象,可以想象成坐标系的平移与旋转。调用canvas.translate(dx,dy)相当于将坐标原点向x,y方向移动了dx,dy的距离,调用canvas.rotate(degree)相当于坐标系顺时针旋转了degree°。
//画刻度
for(int i = 0 ; i < 12 ; i++){
String num = String.valueOf(i == 0 ? 12 : i);
if(i % 3 == 0){
canvas.drawLine(mRadius,0,mRadius - mScaleLengthLong,0, mPaintScaleLong);
}else {
canvas.drawLine(mRadius,0,mRadius - mScaleLengthShort,0, mPaintScaleShort);
}
canvas.drawText(num,mRadius - mScaleLengthLong - mPaintNum.measureText(num) * 2,0, mPaintNum);
//顺时针旋转30度
canvas.rotate(30);
}
根据i值决定是画大刻度(90°的倍数)还是小刻度。每次画完一条之后将坐标系顺时针旋转30°,这样保证每次画的刻度线的坐标不变。
//一秒后重绘
postInvalidateDelayed(1000);
作用是在1000ms(1s)后再次执行View的draw流程,产生指针运动的效果。
上一篇: 1.vue的MVVM响应式原理介绍——vue源码解析
下一篇: SQLServer 镜像功能完全实现