由此starty 也可以推导出来
float starty = (float) (mWidth / 2 - textRedius * Math.cos(Math.PI / 6 * i) + mTextPaint.measureText(times[i]+"") / 2);
cos0度 = 1
cos30度 = 0.8
cos60度 = 0.5
cos90度 = 0
cos120度 = -0.5
cos150度 = -0.8
cos180度 = -1
cos210度 = -0.8
cos240度 = -0.5
cos270度 = 0
cos300度 = 0.5
cos330度 = 0.8
cos360度 = 1
也就是说向上为正 向下为负 和startx 差不多 模拟了一个 值的变化
public class MyClock extends View {
// View 的默认宽高 实际上这里是画圆 所以宽高都是一样的
private static final int DEAFAULT_WIDTH = 200;
private static final int DEAFAULT_HEIGHT = 200;
/**
* 实际测量的宽高
*/
private int mWidth;
private int mHeight;
// 表框的画笔 最外层大圆
private Paint mWaicengPaint;
private Paint mNeicengPaint;
private Paint mShizhenPaint;
private Paint mMiaozhenPaint;
private Paint mFenzhenPaint;
private Paint mTextPaint;
private Paint mKeduPaint;
// 数字
private int[] times = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
// 时
private int shi;
// 分
private int fen;
// 秒
private int miao;
//外层圆的宽度
private float mWaicengyuanWidth = 2f;
private Thread thread;
public MyClock(Context context) {
this(context, null);
}
public MyClock(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyClock(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaints();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = Math.min(mWidth, mHeight);
} else {
mWidth = dip2px(getContext(), DEAFAULT_WIDTH);
}
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = Math.min(mWidth, mHeight);
} else {
mHeight = dip2px(getContext(), DEAFAULT_HEIGHT);
}
mWidth = mHeight = Math.min(mWidth, mHeight);
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Calendar calendar = Calendar.getInstance();
shi = calendar.get(Calendar.HOUR);
fen = calendar.get(Calendar.MINUTE);
miao = calendar.get(Calendar.SECOND);
//画外层圆
drawWaiceng(canvas);
//画刻度
drawKedu(canvas);
//写数
drawText(canvas);
//画时针
drawShizhen(canvas);
//画分针
drawFenzhen(canvas);
//画秒针
drawMiaozhen(canvas);
//画中心圆
drawNeiceng(canvas);
}
private void drawMiaozhen(Canvas canvas) {
//首先是半径 分为2部分 一个是长的部分 一个是短的部分
//半径比时针大一点 比秒小一些
float longRedius = mWidth / 2 - 80;
float shortRedius = 60;
double v = Math.PI / 30; //因为分针是60个位置 时针12个 为5倍 所以这里除以30 时针除以6
float startX = (float) (mWidth / 2 - shortRedius * Math.sin(miao * v));
float startY = (float) (mWidth / 2 + shortRedius * Math.cos(miao * v));
float endX = (float) (mWidth / 2 + longRedius * Math.sin(miao * v));
float endY = (float) (mWidth / 2 - longRedius * Math.cos(miao * v));
canvas.drawLine(startX, startY, endX, endY, mMiaozhenPaint);
}
private void drawFenzhen(Canvas canvas) {
//首先是半径 分为2部分 一个是长的部分 一个是短的部分
//半径比时针大一点 比秒小一些
float longRedius = mWidth / 3;
float shortRedius = 60;
double v = Math.PI / 30; //因为分针是60个位置 时针12个 为5倍 所以这里除以30 时针除以6
float startX = (float) (mWidth / 2 - shortRedius * Math.sin(fen * v));
float startY = (float) (mWidth / 2 + shortRedius * Math.cos(fen * v));
float endX = (float) (mWidth / 2 + longRedius * Math.sin(fen * v));
float endY = (float) (mWidth / 2 - longRedius * Math.cos(fen * v));
canvas.drawLine(startX, startY, endX, endY, mFenzhenPaint);
}
private void drawShizhen(Canvas canvas) {
//首先是半径 分为2部分 一个是长的部分 一个是短的部分
float longRedius = mWidth / 4;
float shortRedius = 60;
double v = Math.PI / 6;
float startX = (float) (mWidth / 2 - shortRedius * Math.sin(shi * v));
float startY = (float) (mWidth / 2 + shortRedius * Math.cos(shi * v));
float endX = (float) (mWidth / 2 + longRedius * Math.sin(shi * v));
float endY = (float) (mWidth / 2 - longRedius * Math.cos(shi * v));
canvas.drawLine(startX, startY, endX, endY, mShizhenPaint);
}
// 写数 方法1***********************************************
private void drawText(Canvas canvas) {
int textRedius = mWidth / 2 - 50;//文字构成的圆的半径
for (int i = 0; i < 12; i++) {
// Math.PI 就是 π 3.1415926 实际上他就是180°
float startX = (float) (mWidth / 2 + textRedius * Math.sin(Math.PI / 6 * i) - mTextPaint.measureText(times[i] + "") / 2);
float starty = (float) (mWidth / 2 - textRedius * Math.cos(Math.PI / 6 * i) + mTextPaint.measureText(times[i] + "") / 2);
canvas.drawText(times[i] + "", startX, starty, mTextPaint);
}
}
private void drawNeiceng(Canvas canvas) {
canvas.drawCircle(mWidth / 2, mWidth / 2, 10, mNeicengPaint);
}
private void drawKedu(Canvas canvas) {
//因为刻度需要旋转画布 首先要保存现在的状态 画完之后 恢复状态
canvas.save();
//刻度的长度
float mKeduLeng;
int a = 0;
// 0-60 60 个刻度
for (int i = 0; i < 60; i++) {
//每5个会有一个较粗
if (i % 5 == 0) {
mKeduPaint.setStrokeWidth(5);
mKeduLeng = 20f;
// 写数 方法2***********************************************
//刻度最大的20 处写数 40是自己定的间距
// String text = times[a]+"";
// a++;
// canvas.drawText(text, mWidth/2-(mTextPaint.measureText(text)/2), 20+mWaicengyuanWidth+40, mTextPaint);
} else {
mKeduPaint.setStrokeWidth(2);
mKeduLeng = 10f;
}
canvas.drawLine(mWidth / 2.f, mWaicengyuanWidth, mWidth / 2.f,
mWaicengyuanWidth + mKeduLeng, mKeduPaint);
// 每画一次就旋转360/60 度
canvas.rotate(360 / 60f, mWidth / 2, mHeight / 2);
}
// 恢复状态
canvas.restore();
}
private void drawWaiceng(Canvas canvas) {
canvas.drawCircle(mWidth / 2.f, mHeight / 2.f, mWidth / 2, mWaicengPaint);
}
/**
* 根据手机的分辨率从 dip 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* 初始化所有用到的画笔
*/
private void initPaints() {
//画最外层大圆的画笔
// 黑色
mWaicengPaint = new Paint();
mWaicengPaint.setAntiAlias(true);//抗锯齿
mWaicengPaint.setDither(true);//防抖动
mWaicengPaint.setColor(Color.BLACK);
mWaicengPaint.setStyle(Paint.Style.STROKE);
mWaicengPaint.setStrokeWidth(mWaicengyuanWidth);
//画刻度的画笔
// 黑色
mKeduPaint = new Paint();
mKeduPaint.setAntiAlias(true);//抗锯齿
mKeduPaint.setDither(true);//防抖动
mKeduPaint.setColor(Color.BLACK);
mKeduPaint.setStyle(Paint.Style.STROKE);
mKeduPaint.setStrokeWidth(2); //这个会被修改
//画内层小圆的画笔
// 黑色
mNeicengPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
mNeicengPaint.setDither(true);//防抖动
mNeicengPaint.setColor(Color.BLACK);
mNeicengPaint.setStyle(Paint.Style.FILL);
//画时针的画笔
// 红色
mShizhenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
mShizhenPaint.setDither(true);//防抖动
mShizhenPaint.setColor(Color.RED);
mShizhenPaint.setStyle(Paint.Style.STROKE);
mShizhenPaint.setStrokeWidth(10);
//画分针的画笔
// 橙色
mFenzhenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
mFenzhenPaint.setDither(true);//防抖动
mFenzhenPaint.setColor(Color.BLUE);
mFenzhenPaint.setStyle(Paint.Style.STROKE);
mFenzhenPaint.setStrokeWidth(6);
//画秒针的画笔
// 黄色
mMiaozhenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
mMiaozhenPaint.setDither(true);//防抖动
mMiaozhenPaint.setColor(Color.BLACK);
mMiaozhenPaint.setStyle(Paint.Style.STROKE);
mMiaozhenPaint.setStrokeWidth(2);
//画数字的画笔
// 黑色
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
mTextPaint.setDither(true);//防抖动
mTextPaint.setColor(Color.BLACK);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextSize(40);
}
private boolean canChange = true;
Handler mHandler = new Handler() {
@SuppressLint("HandlerLeak")
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
int what = msg.what;
if (what == 0) {
if (canChange) {
invalidate();
} else {
}
}
}
};
private boolean isStart = false;
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
thread = new Thread() {
@Override
public void run() {
super.run();
while (true) {
try {
Thread.sleep(1000);
mHandler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mHandler.removeCallbacksAndMessages(null);
//中断线程
thread.interrupt();
}
public void start() {
if (isStart) {
setCanChange(true);
} else {
thread.start();
isStart = true;
}
}
public void stop() {
setCanChange(false);
}
public void setCanChange(boolean canChange) {
this.canChange = canChange;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyClock mClock = findViewById(R.id.mClock);
Button butStart = findViewById(R.id.butStart);
Button butEnd = findViewById(R.id.butEnd);
butStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mClock.start();
}
});
butEnd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mClock.stop();
}
});
}
}
<?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"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<com.as.demo_ok69_clock.MyClock
android:id="@+id/mClock"
android:layout_width="match_parent"
android:layout_height="300dp" />
<Button
android:id="@+id/butStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="开始" />
<Button
android:id="@+id/butEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="暂停" />
</LinearLayout>