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();
}
}