Android – [SelfView] 自定义圆盘指针时钟
ps:
简约圆盘指针时钟,颜色可调、自由搭配;支持阿拉伯数字、罗马数字刻度显示;
效果图
使用:
<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();
}
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);
}
private void drawPoint(Canvas canvas, int degree, float rateLen, float rateTail, float rateWidth) {
double radians = Math.toRadians(degree);
int endX = (int) (centerX + radius * Math.cos(radians) * rateLen);
int endY = (int) (centerY + radius * Math.sin(radians) * rateLen);
canvas.save();
paintMain.setStrokeWidth(radius / rateWidth);
canvas.rotate((-90), centerX, centerY);
canvas.drawLine(centerX, centerY, endX, endY, paintMain);
radians = Math.toRadians(degree - 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);
}
public void startPlay() {
startTask();
}
public void pausePlay() {
pauseTask();
}
public void continuePlay() {
startTask();
}
public void releaseView() {
stopTask();
}
}