Android 画一个闹钟

结果图

先上结果图(下面的代码是没有数字左下角的点的,这个点就是为了提醒文字的(x,y)是它的左下角)

画闹钟其实就是自定义一个View,重写它的onDraw()

我创建了

复制代码
public class ClockView extends View 

重写onDraw()方法如下:

须知:画布的(0,0)在左上角。

画外面的大圆圈就不说了,这个圈可画可不画。

其中x,y是圆心

java 复制代码
    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画小时刻度线

java 复制代码
        /*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获取文字矩阵

java 复制代码
   //检索文本边界框并存储到边界。在边界(由调用者分配)中返回包含所有字符的最小矩形,隐含原点为 (0,0)。
    public void getTextBounds(String text, int start, int end, Rect bounds)

下面是画数字代码

java 复制代码
//画数字
        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);
        }

画小圆圈

下面是画小圆圈的,就是时钟分针秒针起点的圆心

java 复制代码
//画圆
        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度,以此计算每根针应该在什么位置就好。

java 复制代码
    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线程。

java 复制代码
    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();   //刷新重绘
        }
    };

全代码

View

java 复制代码
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);
    }
}

Activity

java 复制代码
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);
    }
}

布局文件

html 复制代码
<?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>
相关推荐
sunnyday04266 分钟前
Spring Boot 项目中使用 Dynamic Datasource 实现多数据源管理
android·spring boot·后端
幽络源小助理1 小时前
下载安装AndroidStudio配置Gradle运行第一个kotlin程序
android·开发语言·kotlin
inBuilder低代码平台1 小时前
浅谈安卓Webview从初级到高级应用
android·java·webview
豌豆学姐1 小时前
Sora2 短剧视频创作中如何保持人物一致性?角色创建接口教程
android·java·aigc·php·音视频·uniapp
白熊小北极2 小时前
Android Jetpack Compose折叠屏感知与适配
android
HelloBan2 小时前
setHintTextColor不生效
android
洞窝技术4 小时前
从0到30+:智能家居配网协议融合的实战与思考
android
QING6184 小时前
SupervisorJob子协程异常处理机制 —— 新手指南
android·kotlin·android jetpack
毕设源码-朱学姐5 小时前
【开题答辩全过程】以 基于安卓的停车位管理系统与设计为例,包含答辩的问题和答案
android
PWRJOY5 小时前
解决Flutter构建安卓项目卡在Flutter: Running Gradle task ‘assembleDebug‘...:替换国内 Maven 镜像
android·flutter·maven