先来一个效果图
先建一个类 继承自 View 重写 View(Context context, @Nullable AttributeSet attrs)方法,在次方法中初始化画笔工具与时间
private void initPaint() {
paint =new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(Color.parseColor("#12ff46"));
mCalendar=Calendar.getInstance();
}
设置计时器,定时刷新View
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
invalidate();
}
},0,1000);
根据界面的宽高以中心点为圆点,设置合理的半径画圆
int w=getWidth();
int h=getHeight();
r=w/2-60;
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(15);
canvas.drawCircle(w/2,h/2,r, paint);//时钟圆盘
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(w/2,h/2,20, paint);//中心小圆覆盖点
为了美观在中心点画一个小圆(此效果可以放到最后画,用以覆盖时针,分针,秒针在中心位置的交叉点)
然后画12至11的刻度(1-12),flow是角度的偏移量,取值 Math.PI,然后画各时刻之间的分钟标记,以及时刻的数字
for(int i=0;i<hours.length;i++){
paint.setStrokeWidth(15);
tx=(w/2+(float)Math.sin(-Math.PI*30*i/180+flow)*(r-indicatorSize));
ty= (h/2+(float)Math.cos(-Math.PI*30*i/180+flow)*(r-indicatorSize));
sx=(float)(w/2+Math.ceil(Math.sin(-Math.PI*30*i/180+flow)*r));
sy=(float)(h/2+Math.ceil(Math.cos(-Math.PI*30*i/180+flow)*r) );
//小时刻度点
canvas.drawLine(tx,ty,sx,sy, paint);
// //时钟中心点与小时刻度点的连接线
// paint.setStrokeWidth(1);
// canvas.drawLine(w/2,h/2,sx,sy,paint);
paint.setStrokeWidth(5);
for(int j=0;j<5;j++){
tx=(w/2+(float)Math.sin(-Math.PI*(30*i+j*6)/180+flow)*(r-indicatorSize/2));
ty= (h/2+(float)Math.cos(-Math.PI*(30*i+j*6)/180+flow)*(r-indicatorSize/2));
sx=(float)(w/2+Math.ceil(Math.sin(-Math.PI*(30*i+j*6)/180+flow)*r));
sy=(float)(h/2+Math.ceil(Math.cos(-Math.PI*(30*i+j*6)/180+flow)*r) );
//分钟/秒针刻度点
paint.setStrokeWidth(5);
canvas.drawLine(tx,ty,sx,sy, paint);
// //时钟中心点与分钟/秒刻度点的连接线
// paint.setStrokeWidth(1);
// canvas.drawLine(w/2,h/2,sx,sy,paint);
}
paint.setStrokeWidth(1);
tx=(float)(w/2+Math.sin(-Math.PI*30*i/180+flow)*(r-60));
ty=(float) (h/2+Math.cos(-Math.PI*30*i/180+flow)*(r-60));
paint.setStrokeWidth(1);
paint.setTextSize(40);
canvas.drawText(hours[i],tx- paint.measureText(hours[i])/2,ty+ paint.getTextSize()/2, paint);
}
获取当前时间
//获取当前时间
mCalendar.setTimeInMillis(System.currentTimeMillis());
ho=mCalendar.get(Calendar.HOUR);
m=mCalendar.get(Calendar.MINUTE);
s=mCalendar.get(Calendar.SECOND);
将时分秒画到界面上
//时针
paint.setStrokeWidth(20);
canvas.drawLine(w/2,h/2,(float)(w/2+Math.sin(-(Math.PI*30*(ho+m/60f))/180+flow)*(r*5f/12)),
(float) (h/2+Math.cos(-(Math.PI*30*(ho+m/60f))/180+flow)*(r*5f/12)), paint);
//分针
paint.setStrokeWidth(10);
canvas.drawLine(w/2,h/2,(float)(w/2+Math.sin(-(Math.PI*6*(m+s/60f))/180+flow)*(r*7f/12)),
(float) (h/2+Math.cos(-(Math.PI*6*(m+s/60f))/180+flow)*(r*7f/12)), paint);
//秒针
paint.setStrokeWidth(5);
canvas.drawLine(w/2,h/2,(float)(w/2+Math.sin(-(Math.PI*6*s)/180+flow)*(r*17f/24)),
(float) (h/2+Math.cos(-(Math.PI*6*s)/180+flow)*(r*17f/24)), paint);
然后画时钟顶部的小部件
//顶部小部件
paint.setStrokeWidth(20);
canvas.drawLine(w/2,h/2-r,w/2,h/2-r-40, paint);
canvas.drawLine(w/2-60,h/2-r-40,w/2+60,h/2-r-40, paint);
然后可以画内部右侧的年月日
paint.setStrokeWidth(2);
paint.setTextSize(35);
paint.setStyle(Paint.Style.FILL);
String p=getNumberS(mCalendar.get(Calendar.YEAR))
+" - "+getNumberS(mCalendar.get(Calendar.MONTH)+1)
+" - "+getNumberS(mCalendar.get(Calendar.DATE));
canvas.drawText(p,w/2+r/3,h/2+paint.getTextSize()/2,paint);
private String getNumberS(int n){
if(n>=10){
return n+"";
}else {
return "0"+n;
}
}
下面是整个自定义View的代码,为了代码的简洁 -(Math.PI*6*(m+s/60f))/180+flow 此类计算在代码中用 angle 替换了,上面的各代码片段为了描述具体的计算方式未规整
package com.dalor.drawcanvas;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import java.util.*;
/**
* 作者: 吴昶 .
* 时间: 2019/1/10 16:29
* 功能简介:
*/
public class DrawTestView extends View {
private Paint paint;
private float r=60;//圆半径
private double flow=Math.PI;//偏差角度
private int indicatorSize=20;//小刻度基准长度
private String[] hours=new String[]{"12","1","2","3","4","5","6","7","8","9","10","11"};
private Calendar mCalendar;
private float tx;//线点
private float ty;
private float sx;//线点
private float sy;
private int ho;//小时
private int m;//分钟
private int s;//秒数
private double angle;//各时刻对应的角度
public DrawTestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
//计时器,每秒钟刷新一次界面
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
invalidate();
}
},0,1000);
}
private void initPaint() {
paint =new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(Color.parseColor("#12ff46"));
mCalendar=Calendar.getInstance();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w=getWidth();
int h=getHeight();
r=w/2-60;
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(15);
canvas.drawCircle(w/2,h/2,r, paint);//时钟圆盘
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(w/2,h/2,20, paint);//中心小圆覆盖点
paint.setStyle(Paint.Style.STROKE);
for(int i=0;i<hours.length;i++){
paint.setStrokeWidth(15);
angle=-Math.PI*30*i/180+flow;
tx=(w/2+(float)Math.sin(angle)*(r-indicatorSize));
ty= (h/2+(float)Math.cos(angle)*(r-indicatorSize));
sx=(float)(w/2+Math.ceil(Math.sin(angle)*r));
sy=(float)(h/2+Math.ceil(Math.cos(angle)*r) );
//小时刻度点
canvas.drawLine(tx,ty,sx,sy, paint);
//时钟中心点与小时刻度点的连接线
// paint.setStrokeWidth(1);
// canvas.drawLine(w/2,h/2,sx,sy,paint);
paint.setStrokeWidth(5);
for(int j=0;j<5;j++){
angle=-Math.PI*(30*i+j*6)/180+flow;
tx=(w/2+(float)Math.sin(angle)*(r-indicatorSize/2));
ty= (h/2+(float)Math.cos(angle)*(r-indicatorSize/2));
sx=(float)(w/2+Math.ceil(Math.sin(angle)*r));
sy=(float)(h/2+Math.ceil(Math.cos(angle)*r) );
//分钟/秒针刻度点
paint.setStrokeWidth(5);
canvas.drawLine(tx,ty,sx,sy, paint);
//时钟中心点与分钟/秒刻度点的连接线
// paint.setStrokeWidth(1);
// canvas.drawLine(w/2,h/2,sx,sy,paint);
}
paint.setStrokeWidth(1);
angle=-Math.PI*30*i/180+flow;
tx=(float)(w/2+Math.sin(angle)*(r-60));
ty=(float) (h/2+Math.cos(angle)*(r-60));
paint.setStrokeWidth(1);
paint.setTextSize(40);
canvas.drawText(hours[i],tx- paint.measureText(hours[i])/2,ty+ paint.getTextSize()/2, paint);
}
//获取当前时间
mCalendar.setTimeInMillis(System.currentTimeMillis());
ho=mCalendar.get(Calendar.HOUR);
m=mCalendar.get(Calendar.MINUTE);
s=mCalendar.get(Calendar.SECOND);
//时针
paint.setStrokeWidth(20);
angle=-(Math.PI*30*(ho+m/60f))/180+flow;
canvas.drawLine(w/2,h/2,(float)(w/2+Math.sin(angle)*(r*5f/12)),
(float) (h/2+Math.cos(angle)*(r*5f/12)), paint);
//分针
paint.setStrokeWidth(10);
angle=-(Math.PI*6*(m+s/60f))/180+flow;
canvas.drawLine(w/2,h/2,(float)(w/2+Math.sin(angle)*(r*7f/12)),
(float) (h/2+Math.cos(angle)*(r*7f/12)), paint);
//秒针
paint.setStrokeWidth(5);
angle=-(Math.PI*6*s)/180+flow;
canvas.drawLine(w/2,h/2,(float)(w/2+Math.sin(angle)*(r*17f/24)),
(float) (h/2+Math.cos(angle)*(r*17f/24)), paint);
//顶部小部件
paint.setStrokeWidth(20);
canvas.drawLine(w/2,h/2-r,w/2,h/2-r-40, paint);
canvas.drawLine(w/2-60,h/2-r-40,w/2+60,h/2-r-40, paint);
//时钟内部右侧时间展示
paint.setStrokeWidth(2);
paint.setTextSize(35);
paint.setStyle(Paint.Style.FILL);
String p=getNumberS(mCalendar.get(Calendar.YEAR))
+" - "+getNumberS(mCalendar.get(Calendar.MONTH)+1)
+" - "+getNumberS(mCalendar.get(Calendar.DATE));
canvas.drawText(p,w/2+r/3,h/2+paint.getTextSize()/2,paint);
}
private String getNumberS(int n){
if(n>=10){
return n+"";
}else {
return "0"+n;
}
}
}
上面的代码未对控件的宽高做大小判断,使用时请注意横竖屏的问题,可能会出现界面显示不全的问题。
上面的代码已屏蔽刻度线。
使用方式,在布局文件中直接引入
<com.dalor.drawcanvas.DrawTestView
android:id="@+id/dtv_test"
android:layout_width="match_parent"
android:layout_height="match_parent"/>