当前位置: 首页 > article >正文

Android -- [SelfView] 自定义圆盘指针时钟

Android – [SelfView] 自定义圆盘指针时钟

ps:
	简约圆盘指针时钟,颜色可调、自由搭配;支持阿拉伯数字、罗马数字刻度显示;
效果图

在这里插入图片描述

使用:
<!-- 自定义属性参考 attrs.xml 文件 -->
<com.nepalese.harinetest.player.VirgoCircleClock
            android:id="@+id/circleclock"
            android:layout_width="300dp"
            android:layout_height="300dp"
            app:paddingFrame="10dp"
            app:strokeSize="5dp"
            app:offsetMark="-1dp"
            app:offsetText="-1dp"
            app:rSmall="5px"
            app:rBig="8px"
            app:needBg="true"
            app:frameColor="@color/colorBlack30"
            app:bgColor="@color/colorEye"
            app:markColor1="@color/black"
            app:markColor2="@color/colorWhite"
            app:textColor="@color/black"
            app:txtSize="18sp"
            app:displayType="type_num"/>
private VirgoCircleClock circleClock;

//使用
circleClock = findViewById(R.id.circleclock);
circleClock.startPlay();
 
//注销
if (circleClock != null) {
   circleClock.releaseView();
}
码源:
1. attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<declare-styleable name="VirgoCircleClock">
        <!--是否绘制背景 默认透明-->
        <attr name="needBg" format="boolean" />
        <!--背景颜色 默认白色-->
        <attr name="bgColor" format="color|reference"/>
        <!--边框颜色 默认黑色-->
        <attr name="frameColor" format="color|reference"/>
        <!--小刻度颜色-->
        <attr name="markColor1" format="color|reference"/>
        <!--大刻度颜色-->
        <attr name="markColor2" format="color|reference"/>
        <!--文字颜色 & 时针、分针颜色-->
        <attr name="textColor" format="color|reference"/>
        <!--秒针颜色-->
        <attr name="secondColor" format="color|reference"/>
        <!--大、小刻度点半径-->
        <attr name="rBig" format="dimension|reference"/>
        <attr name="rSmall" format="dimension|reference"/>
        <attr name="offsetMark" format="dimension|reference"/>
        <attr name="offsetText" format="dimension|reference"/>
        <!--数字类型-->
        <attr name="displayType" format="integer">
            <enum name="type_num" value="1"/>
            <enum name="type_roma" value="2"/>
        </attr>
        <!--内缩间距-->
        <attr name="paddingFrame" format="dimension|reference"/>
        <!--文字大小-->
        <attr name="txtSize" format="dimension|reference"/>
        <!--边框厚度-->
        <attr name="strokeSize" format="dimension|reference"/>
    </declare-styleable>
</resources>
2. VirgoCircleClock.java
package com.nepalese.harinetest.player;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

import com.nepalese.harinetest.R;

import java.util.Calendar;

public class VirgoCircleClock extends View {
    private static final String TAG = "VirgoCircleClock";

    private static final int DEF_RADIUS_BIG = 3;
    private static final int DEF_RADIUS_SMALL = 2;
    private static final int DEF_OFF_MARK = 2;//刻度与边框间距
    private static final int DEF_OFF_TEXT = 3;//数字与边框间距
    private static final int TYPE_NUM = 1;//阿拉伯数字
    private static final int TYPE_ROMA = 2;//罗马数字
    private static final int DEF_PADDING = 15;//内缩间距
    private static final float DEF_SIZE_TEXT = 18f;//数字大小
    private static final float DEF_FRAME_STROKE = 5f;//边框厚度
    private static final float RATE_HOUR = 0.5f;//时针与半径比例
    private static final float RATE_HOUR_TAIL = 0.05f;//时针尾巴与半径比例
    private static final float RATE_HOUR_WIDTH = 70f;//时针尾巴与半径比
    private static final float RATE_MINUTE = 0.6f;//分针与半径比例
    private static final float RATE_MINUTE_TAIL = 0.08f;//分针尾巴与半径比例
    private static final float RATE_MINUTE_WIDTH = 120f;//分针尾巴与半径比
    private static final float RATE_SECOND = 0.7f;//秒针与半径比例
    private static final float RATE_SECOND_TAIL = 0.1f;//秒针尾巴与半径比例
    private static final float RATE_SECOND_WIDTH = 240f;//秒针宽度与半径比

    private Paint paintMain;//刻度+指针
    private Paint paintFrame;//外环边框
    private Calendar calendar;

    private boolean needBg;//是否绘制背景 默认透明
    private int bgColor;//背景颜色 默认白色
    private int frameColor;//边框颜色
    private int markColor1;//小刻度颜色
    private int markColor2;//大刻度颜色
    private int textColor;//文字颜色 & 时针、分针颜色
    private int secondColor;//秒针颜色
    private int radiusBig, radiusSmall;//大、小刻度点半径
    private int offMark, offText;
    private int markY, txtY;
    private int displayStyle;//数字类型
    private int padding;//内缩间距
    private int radius;//表盘半径
    private int hours, minutes, seconds;//当前时分秒
    private int centerX, centerY;//表盘中心坐标
    private float textSize;//文字大小
    private float strokeSize;//边框厚度

    public VirgoCircleClock(Context context) {
        this(context, null);
    }

    public VirgoCircleClock(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VirgoCircleClock(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VirgoCircleClock);
        needBg = typedArray.getBoolean(R.styleable.VirgoCircleClock_needBg, false);
        bgColor = typedArray.getColor(R.styleable.VirgoCircleClock_bgColor, Color.WHITE);
        frameColor = typedArray.getColor(R.styleable.VirgoCircleClock_frameColor, Color.BLACK);
        markColor1 = typedArray.getColor(R.styleable.VirgoCircleClock_markColor1, Color.BLACK);
        markColor2 = typedArray.getColor(R.styleable.VirgoCircleClock_markColor2, Color.DKGRAY);
        textColor = typedArray.getColor(R.styleable.VirgoCircleClock_textColor, Color.BLACK);
        secondColor = typedArray.getColor(R.styleable.VirgoCircleClock_secondColor, Color.RED);

        radiusBig = typedArray.getDimensionPixelSize(R.styleable.VirgoCircleClock_rBig, DEF_RADIUS_BIG);
        radiusSmall = typedArray.getDimensionPixelSize(R.styleable.VirgoCircleClock_rSmall, DEF_RADIUS_SMALL);
        offMark = typedArray.getDimensionPixelSize(R.styleable.VirgoCircleClock_offsetMark, DEF_OFF_MARK);
        offText = typedArray.getDimensionPixelSize(R.styleable.VirgoCircleClock_offsetText, DEF_OFF_TEXT);
        displayStyle = typedArray.getInteger(R.styleable.VirgoCircleClock_displayType, TYPE_NUM);
        textSize = typedArray.getDimension(R.styleable.VirgoCircleClock_txtSize, DEF_SIZE_TEXT);
        strokeSize = typedArray.getDimension(R.styleable.VirgoCircleClock_strokeSize, DEF_FRAME_STROKE);
        padding = typedArray.getDimensionPixelSize(R.styleable.VirgoCircleClock_paddingFrame, DEF_PADDING);

        initData();
    }

    /**
     * 设置|更改布局时调用
     *
     * @param width  容器宽
     * @param height 容器高
     */
    public void initLayout(int width, int height) {
        Log.d(TAG, "initLayout: " + width + " - " + height);
        //取小
        //表盘直径
        int diameter = Math.min(width, height);

        //半径
        radius = (int) ((diameter - padding - strokeSize) / 2);

        //圆心
        centerX = diameter / 2;
        centerY = diameter / 2;

        markY = (int) (padding + strokeSize + offMark);
        txtY = markY + radiusBig * 2 + offText;
    }

    private void initData() {
        paintMain = new Paint();
        paintMain.setAntiAlias(true);
        paintMain.setStyle(Paint.Style.FILL);

        paintFrame = new Paint();
        paintFrame.setAntiAlias(true);
        paintFrame.setStyle(Paint.Style.STROKE);
        paintFrame.setStrokeWidth(strokeSize);

        calendar = Calendar.getInstance();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w > 0 && h > 0) {
            initLayout(w, h);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (radius < 1) {
            return;
        }
        getTimes();

        //画表盘
        drawPlate(canvas);

        //画指针
        drawPointHour(canvas);
        drawPointMinutes(canvas);
        drawPointSeconds(canvas);
    }

    //刷新时间
    private void getTimes() {
        calendar.setTimeInMillis(System.currentTimeMillis());
        hours = calendar.get(Calendar.HOUR_OF_DAY);
        minutes = calendar.get(Calendar.MINUTE);
        seconds = calendar.get(Calendar.SECOND);
    }

    //画表盘
    private void drawPlate(Canvas canvas) {
        //背景
        if(needBg){
            paintMain.setColor(bgColor);
            canvas.drawCircle(centerX, centerY, radius, paintMain);
        }

        //边框
        paintFrame.setColor(frameColor);
        canvas.drawCircle(centerX, centerY, radius, paintFrame);

        canvas.save();
        //刻度
        for (int i = 0; i < 60; i++) {
            if (i % 5 == 0) {
                //大刻度
                paintMain.setColor(markColor1);
                canvas.drawCircle(centerX - radiusBig / 2f, markY, radiusBig, paintMain);
            } else {
                paintMain.setColor(markColor2);
                canvas.drawCircle(centerX - radiusSmall / 2f, markY, radiusSmall, paintMain);
            }
            canvas.rotate(6, centerX, centerY);
        }

        canvas.restore();

        paintMain.setColor(textColor);
        paintMain.setTextSize(textSize);
        //数字
        for (int i = 1; i <= 12; i++) {
            //计算每个数字所在位置的角度
            double radians = Math.toRadians(30 * i); //将角度转换为弧度,以便计算正弦值和余弦值
            String hourText;
            if (displayStyle == TYPE_ROMA) {
                hourText = getHoursGreece(i);
            } else {
                hourText = String.valueOf(i);
            }

            Rect rect = new Rect(); //获取数字的宽度和高度
            paintMain.getTextBounds(hourText, 0, hourText.length(), rect);
            int textWidth = rect.width();
            int textHeight = rect.height();
            canvas.drawText(hourText,
                    (float) (centerX + (radius - txtY) * Math.sin(radians) - textWidth / 2),
                    (float) (centerY - (radius - txtY) * Math.cos(radians) + textHeight / 2),
                    paintMain); //通过计算出来的坐标进行数字的绘制
        }
    }

    private void drawPointHour(Canvas canvas) {
        //画时针
        drawPoint(canvas, 360 / 12 * hours + (30 * minutes / 60), RATE_HOUR, RATE_HOUR_TAIL, RATE_HOUR_WIDTH);
    }

    private void drawPointMinutes(Canvas canvas) {
        //画分针
        drawPoint(canvas, 360 / 60 * minutes + (6 * seconds / 60), RATE_MINUTE, RATE_MINUTE_TAIL, RATE_MINUTE_WIDTH);
    }

    private void drawPointSeconds(Canvas canvas) {
        paintMain.setColor(secondColor);
        //画秒针
        drawPoint(canvas, 360 / 60 * seconds, RATE_SECOND, RATE_SECOND_TAIL, RATE_SECOND_WIDTH);
    }

    /**
     * 画指针
     *
     * @param canvas    画布
     * @param degree    指针走过角度
     * @param rateLen   正向长度与半径比
     * @param rateTail  尾部长度与半径比
     * @param rateWidth 宽度占半径比
     */
    private void drawPoint(Canvas canvas, int degree, float rateLen, float rateTail, float rateWidth) {
        //角度的计算由当前的小时占用的角度加上分针走过的百分比占用的角度之和
        double radians = Math.toRadians(degree);
        //时针的起点为圆的中点
        //通过三角函数计算时针终点的位置,时针最短,取长度的0.5倍
        int endX = (int) (centerX + radius * Math.cos(radians) * rateLen); //计算直线终点x坐标
        int endY = (int) (centerY + radius * Math.sin(radians) * rateLen); //计算直线终点y坐标
        canvas.save();
        paintMain.setStrokeWidth(radius / rateWidth);
        //初始角度是0,应该从12点钟开始算,所以要逆时针旋转90度
        canvas.rotate((-90), centerX, centerY); // 因为角度是从x轴为0度开始计算的,所以要逆时针旋转90度,将开始的角度调整到与y轴重合
        canvas.drawLine(centerX, centerY, endX, endY, paintMain); //根据起始坐标绘制时针
        radians = Math.toRadians(degree - 180); //时针旋转180度,绘制小尾巴
        endX = (int) (centerX + radius * Math.cos(radians) * rateTail);
        endY = (int) (centerY + radius * Math.sin(radians) * rateTail);
        canvas.drawLine(centerX, centerY, endX, endY, paintMain);
        canvas.restore();
    }

    private String getHoursGreece(int i) {
        switch (i) {
            case 1:
                return "I";
            case 2:
                return "II";
            case 3:
                return "III";
            case 4:
                return "IV";
            case 5:
                return "V";
            case 6:
                return "VI";
            case 7:
                return "VII";
            case 8:
                return "VIII";
            case 9:
                return "IX";
            case 10:
                return "X";
            case 11:
                return "XI";
            case 12:
            default:
                return "XII";
        }
    }

    //===================================================
    private final int MSG_UPDATE_TIME = 1;

    private final Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == MSG_UPDATE_TIME) {
                invalidate();
                startTask();
            }
            return false;
        }
    });

    private void startTask() {
        handler.removeMessages(MSG_UPDATE_TIME);
        handler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, 1000L);
    }

    private void pauseTask() {
        removeMsg();
    }

    private void stopTask() {
        removeMsg();
    }

    private void removeMsg() {
        handler.removeMessages(MSG_UPDATE_TIME);
    }

    //==========================api========================================
    public void startPlay() {
        startTask();
    }

    public void pausePlay() {
        pauseTask();
    }

    public void continuePlay() {
        startTask();
    }

    public void releaseView() {
        stopTask();
    }
}

http://www.kler.cn/a/380278.html

相关文章:

  • 【Maven】——基础入门,插件安装、配置和简单使用,Maven如何设置国内源
  • 基于vue框架的的冷链食品物流信息管理系统v81wb(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
  • MySQL45讲 第十四讲 count(*)这么慢,我该怎么办?
  • Python 条件语句
  • 嵌入式开发之文件I/O
  • DFS求解迷宫最长移动路线
  • qt QStatusBar详解
  • k8s 查看cpu使用率最高的pod
  • Hive自定义函数—剔除周日周六(小时级别)
  • 爬虫学习4
  • Vue中ref、reactive、toRef、toRefs的区别
  • IoTDB时序数据库使用
  • R 环境安装
  • 103 - Lecture 2 Table and Data Part 1
  • 初识JDBC
  • 深度学习基础知识-全连接层
  • Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用
  • python之正则表达式总结
  • 一键AI换衣-可图AI试衣
  • qt QSplitter详解
  • MySQL 索引的底层实现原理与优化策略
  • python 爬虫0基础入门 (爬虫基础知识)
  • 深度学习中的迁移学习
  • 使用Kafka构建大规模消息传递系统
  • SpringBoot+Shirp的权限管理
  • 云专线优势有哪些?对接入网络有什么要求?