先上结果图(下面的代码是没有数字左下角的点的,这个点就是为了提醒文字的(x,y)是它的左下角)
?画闹钟其实就是自定义一个View,重写它的onDraw()
我创建了
public class ClockView extends View
重写onDraw()方法如下:
须知:画布的(0,0)在左上角。
画外面的大圆圈就不说了,这个圈可画可不画。
其中x,y是圆心
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/* begin: 画外面大圆圈 */
Paint paintCircle = new Paint();
paintCircle.setColor(Color.BLACK);
paintCircle.setStrokeWidth(5);
paintCircle.setStyle(Paint.Style.STROKE);
int x = wm.getDefaultDisplay().getWidth() / 2; //x轴中点
int y = wm.getDefaultDisplay().getHeight() / 2; //y轴中点
int r = Math.min(x, y) - 10;
canvas.drawCircle(x, y, r, paintCircle);
/*end*/
}
须知:
(1)闹钟的刻度有60条;(2)每5条就是小时刻度(加粗加长);(3)每一条隔着360/60度。
思路:
每次画刻度都将cancas旋转360/60度。逢%5==0画小时刻度线
/*begin 画刻度 60条*/
Paint paint = new Paint();
for (int i = 0; i < 60; i++) {
if (i % 5 == 0) {
paint.setStrokeWidth(8);
canvas.drawLine(x, y - r, x, y - r + 40, paint); //小时刻度线长度为40
} else {
paint.setStrokeWidth(4);
canvas.drawLine(x, y - r, x, y - r + 20, paint); //刻度线长度为20
}
canvas.rotate(6, x, y); //以(x,y)为中心旋转6度
}
/*end*/
须知:
(1)要考虑到画数字的(x,y)并不是其中心点,而是左下角,画的时候需要调整数字位置为(x-文字宽度/2,y+文字高度/2)
?使用getTextBounds获取文字矩阵
//检索文本边界框并存储到边界。在边界(由调用者分配)中返回包含所有字符的最小矩形,隐含原点为 (0,0)。
public void getTextBounds(String text, int start, int end, Rect bounds)
?下面是画数字代码
//画数字
for (int i = 1; i <= 12; i++) {
paint.setStrokeWidth(3);
paint.setTextSize(40);
double radius = Math.toRadians(30 * i); //角度转为弧度,不转是算不出正确结果的
Rect rect = new Rect();
paint.getTextBounds(i + "", 0, (i + "").length(), rect); 获取文字的bounds
//注意:默认情况下文字的x,y是左下角
canvas.drawText(i + "", (int) (x + Math.sin(radius) * (r - 70) - rect.width() / 2), (int) (y - Math.cos(radius) * (r - 70) + rect.height() / 2), paint);
}
下面是画小圆圈的,就是时钟分针秒针起点的圆心
//画圆
paint.setStrokeWidth(5);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(x, y, 20, paint);
drawHand(canvas, x, y, r);
针终点图计算原理如下
?运用以上公式再结合画刻度线得知的每一刻度都是360/60度,就可得知每一秒占用了6度,以此计算每根针应该在什么位置就好。
public void drawHand(Canvas canvas, int x, int y, int r) {
/*begin 获取时间*/
Calendar calendar = Calendar.getInstance();
int second = calendar.get(Calendar.SECOND);
int minute = calendar.get(Calendar.MINUTE);
int hour = calendar.get(Calendar.HOUR);
double angleSecond = Math.toRadians(second * 360 / 60);
double angleMinute = Math.toRadians(minute * 360 / 60);
double angleHour = Math.toRadians(hour * 360 / 12);
Paint paint = new Paint();
//画时针
paint.setStrokeWidth(30);
paint.setColor(0xff991339);
paint.setStrokeCap(Paint.Cap.ROUND);
//(r-200)就是时针的长度
canvas.drawLine(x, y, (int) (x + Math.sin(angleHour) * (r - 200)), (int) (y - Math.cos(angleHour) * (r - 200)), paint);
//画分针
paint.setStrokeWidth(20);
paint.setColor(Color.BLACK);
paint.setStrokeCap(Paint.Cap.ROUND);
//(r-150)就是分针的长度
canvas.drawLine(x, y, (int) (x + Math.sin(angleMinute) * (r - 150)), (int) (y - Math.cos(angleMinute) * (r - 150)), paint);
//画秒针
paint.setStrokeWidth(10);
paint.setColor(Color.BLUE);
paint.setStrokeCap(Paint.Cap.ROUND);
//(r-100)就是秒针的长度
canvas.drawLine(x, y, (int) (x + Math.sin(angleSecond) * (r - 100)), (int) (y - Math.cos(angleSecond) * (r - 100)), paint);
}
图画完就该让它动起来了
这里用的方法是handler,每秒发送一次message,然后handler调用invalidate()刷新重绘视图。
这里需要找个地方start线程。
private int TIME = 1000; //1s
Thread t = new Thread() {
@Override
public void run() {
try {
while (true) {
h.sendEmptyMessage(0);
sleep(1000);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
Handler h = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
invalidate(); //刷新重绘
}
};
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Calendar;
public class ClockView extends View {
private WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
final String TAG = getClass().getSimpleName();
private int TIME = 1000; //1s
Thread t = new Thread() {
@Override
public void run() {
try {
while (true) {
h.sendEmptyMessage(0);
sleep(1000);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
Handler h = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
invalidate(); //刷新重绘
}
};
public ClockView(Context context) {
super(context);
Log.d(TAG, "ClockView: 1");
}
public ClockView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, "ClockView: 2");
t.start();
}
public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.d(TAG, "ClockView: 3");
}
public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Log.d(TAG, "ClockView: 4");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/* begin: 画外面大圆圈 */
Paint paintCircle = new Paint();
paintCircle.setColor(Color.BLACK);
paintCircle.setStrokeWidth(5);
paintCircle.setStyle(Paint.Style.STROKE);
int x = wm.getDefaultDisplay().getWidth() / 2; //x轴中点
int y = wm.getDefaultDisplay().getHeight() / 2; //y轴中点
int r = Math.min(x, y) - 10;
canvas.drawCircle(x, y, r, paintCircle);
/*end*/
/*begin 画刻度 60条*/
Paint paint = new Paint();
for (int i = 0; i < 60; i++) {
if (i % 5 == 0) {
paint.setStrokeWidth(8);
canvas.drawLine(x, y - r, x, y - r + 40, paint);
} else {
paint.setStrokeWidth(4);
canvas.drawLine(x, y - r, x, y - r + 20, paint);
}
canvas.rotate(6, x, y);
}
/*end*/
//画数字
for (int i = 1; i <= 12; i++) {
paint.setStrokeWidth(3);
paint.setTextSize(40);
double radius = Math.toRadians(30 * i); //角度转为弧度,不转是算不出正确结果的
Rect rect = new Rect();
paint.getTextBounds(i + "", 0, (i + "").length(), rect);
//注意:默认情况下文字的x,y是左下角
canvas.drawText(i + "", (int) (x + Math.sin(radius) * (r - 70) - rect.width() / 2), (int) (y - Math.cos(radius) * (r - 70) + rect.height() / 2), paint);
}
//画圆
paint.setStrokeWidth(5);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(x, y, 20, paint);
drawHand(canvas, x, y, r);
}
public void drawHand(Canvas canvas, int x, int y, int r) {
/*begin 获取时间*/
Calendar calendar = Calendar.getInstance();
int second = calendar.get(Calendar.SECOND);
int minute = calendar.get(Calendar.MINUTE);
int hour = calendar.get(Calendar.HOUR);
double angleSecond = Math.toRadians(second * 360 / 60);
double angleMinute = Math.toRadians(minute * 360 / 60);
double angleHour = Math.toRadians(hour * 360 / 12);
Paint paint = new Paint();
//画时针
paint.setStrokeWidth(30);
paint.setColor(0xff991339);
paint.setStrokeCap(Paint.Cap.ROUND);
//(r-200)就是时针的长度
canvas.drawLine(x, y, (int) (x + Math.sin(angleHour) * (r - 200)), (int) (y - Math.cos(angleHour) * (r - 200)), paint);
//画分针
paint.setStrokeWidth(20);
paint.setColor(Color.BLACK);
paint.setStrokeCap(Paint.Cap.ROUND);
//(r-150)就是分针的长度
canvas.drawLine(x, y, (int) (x + Math.sin(angleMinute) * (r - 150)), (int) (y - Math.cos(angleMinute) * (r - 150)), paint);
//画秒针
paint.setStrokeWidth(10);
paint.setColor(Color.BLUE);
paint.setStrokeCap(Paint.Cap.ROUND);
//(r-100)就是秒针的长度
canvas.drawLine(x, y, (int) (x + Math.sin(angleSecond) * (r - 100)), (int) (y - Math.cos(angleSecond) * (r - 100)), paint);
}
}
import androidx.appcompat.app.AppCompatActivity;;
import android.os.Bundle;
public class CanvasActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_canvas);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CanvasActivity">
<com.example.sim.view.ClockView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>