自定义View合辑(1)-时钟

【自定义View合辑(1)-时钟】先看下效果图:

自定义View合辑(1)-时钟
文章图片
在这里插入图片描述
ClockView 的逻辑并不算复杂,重点在于时钟刻度以及三根指示针的绘制,然后设定一个定时任务每秒刷新绘制即可
一、确定宽高 为 View 设定其默认大小为 DEFAULT_SIZE

//View的默认大小,dp private static final int DEFAULT_SIZE = 320; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int defaultSize = dp2px(DEFAULT_SIZE); int widthSize = getSize(widthMeasureSpec, defaultSize); int heightSize = getSize(heightMeasureSpec, defaultSize); widthSize = heightSize = Math.min(widthSize, heightSize); setMeasuredDimension(widthSize, heightSize); }protected int getSize(int measureSpec, int defaultSize) { int mode = MeasureSpec.getMode(measureSpec); int size = 0; switch (mode) { case MeasureSpec.AT_MOST: { size = Math.min(MeasureSpec.getSize(measureSpec), defaultSize); break; } case MeasureSpec.EXACTLY: { size = MeasureSpec.getSize(measureSpec); break; } case MeasureSpec.UNSPECIFIED: { size = defaultSize; break; } } return size; }

二、初始化画笔 在构造函数中初始化绘制表盘以及文本的画笔
public ClockView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initClockPaint(); initTextPaint(); time = new Time(); timerHandler = new TimerHandler(this); }private void initClockPaint() { clockPaint = new Paint(); clockPaint.setStyle(Paint.Style.STROKE); clockPaint.setAntiAlias(true); clockPaint.setStrokeWidth(aroundStockWidth); }private void initTextPaint() { textPaint = new Paint(); textPaint.setStyle(Paint.Style.FILL); textPaint.setAntiAlias(true); textPaint.setStrokeWidth(12); textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setTextSize(textSize); }

三、绘制 在此处是通过不断转换 Canvas 的坐标系来完成刻度以及指示针的绘制,这相比通过数学计算来计算各个刻度的位置要简单得多
@Override protected void onDraw(Canvas canvas) { //中心点的横纵坐标 float pointWH = getWidth() / 2.0f; //内圆的半径 float radiusIn = pointWH - aroundStockWidth; canvas.translate(pointWH, pointWH); //绘制表盘 if (aroundStockWidth > 0) { clockPaint.setStrokeWidth(aroundStockWidth); clockPaint.setStyle(Paint.Style.STROKE); clockPaint.setColor(aroundColor); canvas.drawCircle(0, 0, pointWH - aroundStockWidth / 2.0f, clockPaint); } clockPaint.setStyle(Paint.Style.FILL); clockPaint.setColor(Color.WHITE); canvas.drawCircle(0, 0, radiusIn, clockPaint); //绘制小短线 canvas.save(); canvas.rotate(-90); float longLineLength = radiusIn / 16.0f; float longStartY = radiusIn - longLineLength; float longStopY = longStartY - longLineLength; float longStockWidth = 2; float temp = longLineLength / 4.0f; float shortStartY = longStartY - temp; float shortStopY = longStopY + temp; float shortStockWidth = longStockWidth / 2.0f; clockPaint.setColor(Color.BLACK); float degrees = 6; for (int i = 0; i <= 360; i += degrees) { if (i % 30 == 0) { clockPaint.setStrokeWidth(longStockWidth); canvas.drawLine(0, longStartY, 0, longStopY, clockPaint); } else { clockPaint.setStrokeWidth(shortStockWidth); canvas.drawLine(0, shortStartY, 0, shortStopY, clockPaint); } canvas.rotate(degrees); } canvas.restore(); //绘制时钟数字 if (textSize > 0) { float x, y; for (int i = 1; i <= 12; i += 1) { textPaint.getTextBounds(String.valueOf(i), 0, String.valueOf(i).length(), rect); float textHeight = rect.height(); float distance = radiusIn - 2 * longLineLength - textHeight; double tempVa = i * 30.0f * Math.PI / 180.0f; x = (float) (distance * Math.sin(tempVa)); y = (float) (-distance * Math.cos(tempVa)); canvas.drawText(String.valueOf(i), x, y + textHeight / 3, textPaint); } }canvas.rotate(-90); clockPaint.setStrokeWidth(2); //绘制时针 canvas.save(); canvas.rotate(hour / 12.0f * 360.0f); canvas.drawLine(-30, 0, radiusIn / 2.0f, 0, clockPaint); canvas.restore(); //绘制分针 canvas.save(); canvas.rotate(minute / 60.0f * 360.0f); canvas.drawLine(-30, 0, radiusIn * 0.7f, 0, clockPaint); canvas.restore(); //绘制秒针 clockPaint.setColor(Color.parseColor("#fff2204d")); canvas.save(); canvas.rotate(second / 60.0f * 360.0f); canvas.drawLine(-30, 0, radiusIn * 0.85f, 0, clockPaint); canvas.restore(); //绘制中心小圆点 clockPaint.setStyle(Paint.Style.FILL); clockPaint.setColor(clockCenterColor); canvas.drawCircle(0, 0, radiusIn / 20.0f, clockPaint); }

四、动画效果 onDraw 方法只是完成了 View 的绘制,此处还需要思考如何令时钟“动”起来。本 Demo 是通过 Handler 来设定定时任务的,当 View 处于可见状态时就每隔一秒主动刷新界面
为了避免内存泄漏问题,此处通过弱引用的形式来引用 ClockView
private static final int MSG_INVALIDATE = 10; private static final class TimerHandler extends Handler {private WeakReference clockViewWeakReference; private TimerHandler(ClockView clockView) { clockViewWeakReference = new WeakReference<>(clockView); }@Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: { Log.e(TAG, "定时任务被触发..."); ClockView view = clockViewWeakReference.get(); if (view != null) { view.onTimeChanged(); view.invalidate(); sendEmptyMessageDelayed(MSG_INVALIDATE, 1000); } break; } } } }private void onTimeChanged() { time.setToNow(); minute = time.minute; hour = time.hour + minute / 60.0f; second = time.second; }

五、适用多时区 为了在系统时区改变时能够进行相应的时间变化,此处还需要监听系统的 Intent.ACTION_TIMEZONE_CHANGED 广播
private final BroadcastReceiver timerBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action != null) { switch (action) { //监听时区的变化 case Intent.ACTION_TIMEZONE_CHANGED: { time = new Time(TimeZone.getTimeZone(intent.getStringExtra("time-zone")).getID()); break; } } } } };

在 View 可见的时候注册广播,不可见的时候就解除注册
@Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Log.e(TAG, "onDetachedFromWindow"); stopTimer(); unregisterTimezoneAction(); }@Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); Log.e(TAG, "onVisibilityChanged visibility: " + visibility); if (visibility == View.VISIBLE) { registerTimezoneAction(); startTimer(); } else { stopTimer(); unregisterTimezoneAction(); } }private void startTimer() { Log.e(TAG, "startTimer 开启定时任务"); timerHandler.removeMessages(MSG_INVALIDATE); timerHandler.sendEmptyMessage(MSG_INVALIDATE); }private void stopTimer() { Log.e(TAG, "stopTimer 停止定时任务"); timerHandler.removeMessages(MSG_INVALIDATE); }private void registerTimezoneAction() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); getContext().registerReceiver(timerBroadcast, filter); }private void unregisterTimezoneAction() { getContext().unregisterReceiver(timerBroadcast); }

喜欢点击+关注哦欢迎大家评论 自定义View合辑(1)-时钟
文章图片

    推荐阅读