Android – [SelfView] 炫酷文字圆盘时钟
ps: 感觉效果满帅的,就自己弄了个!
效果图
使用:
<com.nepalese.virgocomponent.view.VirgoTextClockView
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:vtcTextColorMain="@color/colorRed"
app:vtcTextColorSec="@color/colorGray"
app:vtcTextSizeMain="14sp"
app:vtcTextSizeClock="18sp"
app:vtcOffset="10dp"
app:vtcRadiusH="60dp"
app:vtcRadiusM="125dp"
app:vtcRadiusS="190dp"/>
码源:
1. attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="VirgoTextClockView">
<attr name="vtcTextColorMain" format="color|reference" />
<attr name="vtcTextColorSec" format="color|reference" />
<attr name="vtcColorBg" format="color|reference" />
<attr name="vtcTextSizeMain" format="dimension|reference" />
<attr name="vtcTextSizeClock" format="dimension|reference" />
<attr name="vtcRadiusH" format="dimension|reference" />
<attr name="vtcRadiusM" format="dimension|reference" />
<attr name="vtcRadiusS" format="dimension|reference" />
<attr name="vtcOffset" format="dimension|reference" />
</declare-styleable>
</resources>
2. VirgoTextClockView.java
package com.nepalese.virgocomponent.view;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.nepalese.virgocomponent.R;
import com.nepalese.virgocomponent.component.bean.ClockBean;
import java.util.Calendar;
import java.util.Date;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class VirgoTextClockView extends View {
private static final int MIN_WIDTH = 200;
private static final long INTERVAL_UPDATE = 1000L;
private static final String[] HOURS = {"一点", "二点", "三点", "四点", "五点", "六点", "七点", "八点", "九点", "十点", "十一点", "十二点"};
private static final String[] MINUTES = {"一分", "二分", "三分", "四分", "五分", "六分", "七分", "八分", "九分", "十分",
"十一分", "十二分", "十三分", "十四分", "十五分", "十六分", "十七分", "十八分", "十九分", "二十分",
"二十一分", "二十二分", "二十三分", "二十四分", "二十五分", "二十六分", "二十七分", "二十八分", "二十九分", "三十分",
"三十一分", "三十二分", "三十三分", "三十四分", "三十五分", "三十六分", "三十七分", "三十八分", "三十九分", "四十分",
"四十一分", "四十二分", "四十三分", "四十四分", "四十五分", "四十六分", "四十七分", "四十八分", "四十九分", "五十分",
"五十一分", "五十二分", "五十三分", "五十四分", "五十五分", "五十六分", "五十七分", "五十八分", "五十九分", ""};
private static final String[] SECONDS = {"一秒", "二秒", "三秒", "四秒", "五秒", "六秒", "七秒", "八秒", "九秒", "十秒",
"十一秒", "十二秒", "十三秒", "十四秒", "十五秒", "十六秒", "十七秒", "十八秒", "十九秒", "二十秒",
"二十一秒", "二十二秒", "二十三秒", "二十四秒", "二十五秒", "二十六秒", "二十七秒", "二十八秒", "二十九秒", "三十秒",
"三十一秒", "三十二秒", "三十三秒", "三十四秒", "三十五秒", "三十六秒", "三十七秒", "三十八秒", "三十九秒", "四十秒",
"四十一秒", "四十二秒", "四十三秒", "四十四秒", "四十五秒", "四十六秒", "四十七秒", "四十八秒", "四十九秒", "五十秒",
"五十一秒", "五十二秒", "五十三秒", "五十四秒", "五十五秒", "五十六秒", "五十七秒", "五十八秒", "五十九秒", ""};
private Paint mPaintMain;
private Paint mPaintSecond;
private Paint mPaintClock;
private ValueAnimator mAnimator;
private int mColorMain;
private int mColorSecond;
private int mColorBg;
private int mWidth, mHeight;
private int mH, mM, mS;
private int mH24;
private int mRadiusH, mRadiusM, mRadiusS;
private String mWeek, mDate;
private float mDegreeH, mDegreeM, mDegreeS;
private float mTextSizeMain;
private float mTextSizeClock;
private float mOffset;
private float mCenterHeight;
public VirgoTextClockView(Context context) {
this(context, null);
}
public VirgoTextClockView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VirgoTextClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.VirgoTextClockView);
mColorMain = typedArray.getColor(R.styleable.VirgoTextClockView_vtcTextColorMain, Color.WHITE);
mColorSecond = typedArray.getColor(R.styleable.VirgoTextClockView_vtcTextColorSec, Color.GRAY);
mColorBg = typedArray.getColor(R.styleable.VirgoTextClockView_vtcColorBg, Color.BLACK);
mTextSizeMain = typedArray.getDimension(R.styleable.VirgoTextClockView_vtcTextSizeMain, 25f);
mTextSizeClock = typedArray.getDimension(R.styleable.VirgoTextClockView_vtcTextSizeClock, 35f);
mRadiusH = typedArray.getDimensionPixelSize(R.styleable.VirgoTextClockView_vtcRadiusH, 140);
mRadiusM = typedArray.getDimensionPixelSize(R.styleable.VirgoTextClockView_vtcRadiusM, 220);
mRadiusS = typedArray.getDimensionPixelSize(R.styleable.VirgoTextClockView_vtcRadiusS, 320);
mOffset = typedArray.getDimensionPixelSize(R.styleable.VirgoTextClockView_vtcOffset, 8);
typedArray.recycle();
mPaintMain = new Paint();
mPaintMain.setAntiAlias(true);
mPaintMain.setColor(mColorMain);
mPaintMain.setTextSize(mTextSizeMain);
mPaintMain.setStyle(Paint.Style.FILL);
mPaintSecond = new Paint();
mPaintSecond.setAntiAlias(true);
mPaintSecond.setColor(mColorSecond);
mPaintSecond.setTextSize(mTextSizeMain);
mPaintSecond.setStyle(Paint.Style.FILL);
mPaintClock = new Paint();
mPaintClock.setAntiAlias(true);
mPaintClock.setColor(mColorMain);
mPaintClock.setTextSize(mTextSizeClock);
mPaintClock.setStyle(Paint.Style.FILL);
initAnimator();
getCurTime();
}
private void initAnimator() {
mAnimator = ValueAnimator.ofFloat(6f, 0f);
mAnimator.setDuration(150);
mAnimator.setInterpolator(new LinearInterpolator());
}
private void getCurTime() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
mH24 = calendar.get(Calendar.HOUR_OF_DAY);
mH = calendar.get(Calendar.HOUR);
mM = calendar.get(Calendar.MINUTE);
mS = calendar.get(Calendar.SECOND);
parseWeek(calendar.get(Calendar.DAY_OF_WEEK));
parseDate(calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH));
calculateDegree();
}
private void calculateDegree() {
mDegreeH = -360 / 12f * (mH - 1);
mDegreeM = -360 / 60f * (mM - 1);
mDegreeS = -360 / 60f * (mS - 1);
}
private void parseWeek(int week) {
switch (week) {
case 1:
mWeek = "星期日";
break;
case 2:
mWeek = "星期一";
break;
case 3:
mWeek = "星期二";
break;
case 4:
mWeek = "星期三";
break;
case 5:
mWeek = "星期四";
break;
case 6:
mWeek = "星期五";
break;
case 7:
mWeek = "星期六";
break;
}
}
private void parseDate(int month, int day) {
mDate = foramtTime(month, day, "/");
}
private String foramtTime(int h, int m, String tap) {
String sH, sM;
if (h < 10) {
sH = "0" + h;
} else {
sH = String.valueOf(h);
}
if (m < 10) {
sM = "0" + m;
} else {
sM = String.valueOf(m);
}
return sH + tap + sM;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getRealSize(widthMeasureSpec);
mHeight = getRealSize(heightMeasureSpec);
mHeight = mWidth = Math.min(mWidth, mHeight);
mCenterHeight = (mHeight + getFontHeight(mTextSizeMain)) / 2f;
setMeasuredDimension(mWidth, mHeight);
}
private int getRealSize(int measureSpec) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.UNSPECIFIED) {
result = MIN_WIDTH;
} else {
result = size;
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(mColorBg);
drawTimeInfo(canvas);
drawHour(canvas);
drawMinute(canvas);
drawSecond(canvas);
}
private void drawTimeInfo(Canvas canvas) {
String time = foramtTime(mH24, mM, "\t:\t");
canvas.drawText(time, (mWidth - mPaintClock.measureText(time)) / 2f,
(mHeight - getFontHeight(mTextSizeClock)) / 2f + mOffset, mPaintClock);
String date = mDate + "\t" + mWeek;
canvas.drawText(date, (mWidth - mPaintMain.measureText(date)) / 2f,
(mHeight + getFontHeight(mTextSizeMain)) / 2f + mOffset, mPaintMain);
}
private void drawHour(Canvas canvas) {
canvas.save();
canvas.rotate(mDegreeH, mWidth / 2f, mHeight / 2f);
for (int i = 0; i < HOURS.length; i++) {
canvas.save();
canvas.rotate(30 * i, mWidth / 2f, mHeight / 2f);
if (i + 1 == mH || (i == 11 && mH == 0)) {
canvas.drawText(HOURS[i], mRadiusH + mWidth / 2f, mCenterHeight, mPaintMain);
} else {
canvas.drawText(HOURS[i], mRadiusH + mWidth / 2f, mCenterHeight, mPaintSecond);
}
canvas.restore();
}
canvas.restore();
}
private void drawMinute(Canvas canvas) {
canvas.save();
canvas.rotate(mDegreeM, mWidth / 2f, mHeight / 2f);
for (int i = 0; i < MINUTES.length; i++) {
canvas.save();
canvas.rotate(6 * i, mWidth / 2f, mHeight / 2f);
if (i + 1 == mM) {
canvas.drawText(MINUTES[i], mRadiusM + mWidth / 2f, mCenterHeight, mPaintMain);
} else {
canvas.drawText(MINUTES[i], mRadiusM + mWidth / 2f, mCenterHeight, mPaintSecond);
}
canvas.restore();
}
canvas.restore();
}
private void drawSecond(Canvas canvas) {
canvas.save();
canvas.rotate(mDegreeS, mWidth / 2f, mHeight / 2f);
for (int i = 0; i < SECONDS.length; i++) {
canvas.save();
canvas.rotate(6 * i, mWidth / 2f, mHeight / 2f);
if (i + 1 == mS) {
canvas.drawText(SECONDS[i], mRadiusS + mWidth / 2f, mCenterHeight, mPaintMain);
} else {
canvas.drawText(SECONDS[i], mRadiusS + mWidth / 2f, mCenterHeight, mPaintSecond);
}
canvas.restore();
}
canvas.restore();
}
private float getFontHeight(float textSize) {
Paint paint = new Paint();
paint.setTextSize(textSize);
Paint.FontMetrics fm = paint.getFontMetrics();
return fm.descent - fm.ascent;
}
private final Runnable clockTask = new Runnable() {
@Override
public void run() {
updateClock();
handler.postDelayed(clockTask, INTERVAL_UPDATE);
}
};
private void updateClock() {
getCurTime();
float hd = mDegreeH;
float md = mDegreeM;
float sd = mDegreeS;
mAnimator.removeAllUpdateListeners();
mAnimator.addUpdateListener(animation -> {
float av = (float) animation.getAnimatedValue();
if (mM == 0 && mS == 0) {
mDegreeH = hd + av * 5;
}
if (mS == 0) {
mDegreeM = md + av;
}
mDegreeS = sd + av;
invalidate();
});
mAnimator.start();
}
private final Handler handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAnimator.removeAllUpdateListeners();
stopClock();
}
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility != VISIBLE) {
stopClock();
} else {
startClock();
}
}
public void startClock() {
stopClock();
handler.post(clockTask);
}
public void stopClock() {
handler.removeCallbacks(clockTask);
}
public void setmColorMain(@ColorInt int mColorMain) {
this.mColorMain = mColorMain;
mPaintMain.setColor(mColorMain);
mPaintClock.setColor(mColorMain);
}
public void setmColorSecond(@ColorInt int mColorSecond) {
this.mColorSecond = mColorSecond;
mPaintSecond.setColor(mColorSecond);
}
public void setmColorBg(@ColorInt int mColorBg) {
this.mColorBg = mColorBg;
}
public void setmRadiusH(int mRadiusH) {
this.mRadiusH = mRadiusH;
}
public void setmRadiusM(int mRadiusM) {
this.mRadiusM = mRadiusM;
}
public void setmRadiusS(int mRadiusS) {
this.mRadiusS = mRadiusS;
}
public void setmTextSizeMain(float mTextSizeMain) {
this.mTextSizeMain = mTextSizeMain;
mPaintMain.setTextSize(mTextSizeMain);
mPaintSecond.setTextSize(mTextSizeMain);
}
public void setmTextSizeClock(float mTextSizeClock) {
this.mTextSizeClock = mTextSizeClock;
mPaintClock.setTextSize(mTextSizeClock);
}
public void setmOffset(float mOffset) {
this.mOffset = mOffset;
}
public void setConfig(ClockBean bean) {
this.setmColorMain(Color.parseColor(bean.getColorSelect()));
this.setmColorSecond(Color.parseColor(bean.getColorDefault()));
this.setmTextSizeMain(bean.getSizeClock());
this.setmTextSizeClock(bean.getSizeCenter());
this.setmOffset(bean.getOffset());
this.setmRadiusH(bean.getrHour());
this.setmRadiusM(bean.getrMinute());
this.setmRadiusS(bean.getrSecond());
}
}