Android自定義控件之自定義時(shí)鐘
這個(gè)是我從別的開源項(xiàng)目中挖出來的,真心寫的很不錯(cuò),然后繼續(xù)下來以便不時(shí)之需,直接上代碼:
WatcherBoard.java這個(gè)是自定義的時(shí)鐘類
package cn.xiayiye.custormtext;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.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;import java.util.Calendar;/*** ================================================* 作 者:楊充* 版 本:1.0* 創(chuàng)建日期:2017/8/23* 描 述:自定義鐘表計(jì)時(shí)器* 修訂歷史:* ================================================*/
public class WatcherBoard extends View {private float mRadius; //外圓半徑private float mPadding; //邊距private float mTextSize; //文字大小private float mHourPointWidth; //時(shí)針寬度private float mMinutePointWidth; //分針寬度private float mSecondPointWidth; //秒針寬度private int mPointRadius; // 指針圓角private float mPointEndLength; //指針末尾的長度private int mColorLong; //長線的顏色private int mColorShort; //短線的顏色private int mHourPointColor; //時(shí)針的顏色private int mMinutePointColor; //分針的顏色private int mSecondPointColor; //秒針的顏色private Paint mPaint; //畫筆public WatcherBoard(Context context) {this(context, null);}public WatcherBoard(Context context, AttributeSet attrs) {super(context, attrs);obtainStyledAttrs(attrs); //獲取自定義的屬性init(); //初始化畫筆}private void obtainStyledAttrs(AttributeSet attrs) {TypedArray array = null;try {array = getContext().obtainStyledAttributes(attrs, R.styleable.WatchBoard);mPadding = array.getDimension(R.styleable.WatchBoard_wb_padding, DpToPx(10));mTextSize = array.getDimension(R.styleable.WatchBoard_wb_text_size, SpToPx(16));mHourPointWidth = array.getDimension(R.styleable.WatchBoard_wb_hour_pointer_width, DpToPx(5));mMinutePointWidth = array.getDimension(R.styleable.WatchBoard_wb_minute_pointer_width, DpToPx(3));mSecondPointWidth = array.getDimension(R.styleable.WatchBoard_wb_second_pointer_width, DpToPx(2));mPointRadius = (int) array.getDimension(R.styleable.WatchBoard_wb_pointer_corner_radius, DpToPx(10));mPointEndLength = array.getDimension(R.styleable.WatchBoard_wb_pointer_end_length, DpToPx(10));mColorLong = array.getColor(R.styleable.WatchBoard_wb_scale_long_color, Color.argb(225, 0, 0, 0));mColorShort = array.getColor(R.styleable.WatchBoard_wb_scale_short_color, Color.argb(125, 0, 0, 0));mHourPointColor = array.getColor(R.styleable.WatchBoard_wb_hour_pointer_color, Color.BLACK);mMinutePointColor = array.getColor(R.styleable.WatchBoard_wb_minute_pointer_color, Color.BLACK);mSecondPointColor = array.getColor(R.styleable.WatchBoard_wb_second_pointer_color, Color.RED);} catch (Exception e) {//一旦出現(xiàn)錯(cuò)誤全部使用默認(rèn)值mPadding = DpToPx(10);mTextSize = SpToPx(16);mHourPointWidth = DpToPx(5);mMinutePointWidth = DpToPx(3);mSecondPointWidth = DpToPx(2);mPointRadius = (int) DpToPx(10);mPointEndLength = DpToPx(10);mColorLong = Color.argb(225, 0, 0, 0);mColorShort = Color.argb(125, 0, 0, 0);mMinutePointColor = Color.BLACK;mSecondPointColor = Color.RED;} finally {if (array != null) {array.recycle();}}}//Dp轉(zhuǎn)pxprivate float DpToPx(int value) {return SizeUtils.dp2px(value);}//sp轉(zhuǎn)pxprivate float SpToPx(int value) {return SizeUtils.sp2px(value);}//畫筆初始化private void init() {mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setDither(true);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = 1000; //設(shè)定一個(gè)最小值int widthSize = MeasureSpec.getSize(widthMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED ||heightMeasureSpec == MeasureSpec.AT_MOST || heightMeasureSpec == MeasureSpec.UNSPECIFIED) {try {throw new NoDetermineSizeException("寬度高度至少有一個(gè)確定的值,不能同時(shí)為wrap_content");} catch (NoDetermineSizeException e) {e.printStackTrace();}} else { //至少有一個(gè)為確定值,要獲取其中的最小值if (widthMode == MeasureSpec.EXACTLY) {width = Math.min(widthSize, width);}if (heightMode == MeasureSpec.EXACTLY) {width = Math.min(heightSize, width);}}setMeasuredDimension(width, width);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mRadius = (Math.min(w, h) - getPaddingLeft() - getPaddingRight()) / 2;mPointEndLength = mRadius / 6; //尾部指針默認(rèn)為半徑的六分之一}//繪制外圓背景public void paintCircle(Canvas canvas) {mPaint.setColor(Color.WHITE);mPaint.setStyle(Paint.Style.FILL);canvas.drawCircle(0, 0, mRadius, mPaint);}@Overrideprotected void onDraw(Canvas canvas) {canvas.save();canvas.translate(getWidth() / 2, getHeight() / 2);//繪制外圓背景paintCircle(canvas);//繪制刻度paintScale(canvas);//繪制指針paintPointer(canvas);canvas.restore();//刷新postInvalidateDelayed(1000);}private void paintPointer(Canvas canvas) {Calendar calendar = Calendar.getInstance();int hour = calendar.get(Calendar.HOUR_OF_DAY); //時(shí)int minute = calendar.get(Calendar.MINUTE); //分int second = calendar.get(Calendar.SECOND); //秒int angleHour = (hour % 12) * 360 / 12; //時(shí)針轉(zhuǎn)過的角度int angleMinute = minute * 360 / 60; //分針轉(zhuǎn)過的角度int angleSecond = second * 360 / 60; //秒針轉(zhuǎn)過的角度//繪制時(shí)針canvas.save();canvas.rotate(angleHour); //旋轉(zhuǎn)到時(shí)針的角度RectF rectFHour = new RectF(-mHourPointWidth / 2, -mRadius * 3 / 5, mHourPointWidth / 2, mPointEndLength);mPaint.setColor(mHourPointColor); //設(shè)置指針顏色mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(mHourPointWidth); //設(shè)置邊界寬度canvas.drawRoundRect(rectFHour, mPointRadius, mPointRadius, mPaint); //繪制時(shí)針canvas.restore();//繪制分針canvas.save();canvas.rotate(angleMinute);RectF rectFMinute = new RectF(-mMinutePointWidth / 2, -mRadius * 3.5f / 5, mMinutePointWidth / 2, mPointEndLength);mPaint.setColor(mMinutePointColor);mPaint.setStrokeWidth(mMinutePointWidth);canvas.drawRoundRect(rectFMinute, mPointRadius, mPointRadius, mPaint);canvas.restore();//繪制秒針canvas.save();canvas.rotate(angleSecond);RectF rectFSecond = new RectF(-mSecondPointWidth / 2, -mRadius + 15, mSecondPointWidth / 2, mPointEndLength);mPaint.setColor(mSecondPointColor);mPaint.setStrokeWidth(mSecondPointWidth);canvas.drawRoundRect(rectFSecond, mPointRadius, mPointRadius, mPaint);canvas.restore();//繪制中心小圓mPaint.setStyle(Paint.Style.FILL);mPaint.setColor(mSecondPointColor);canvas.drawCircle(0, 0, mSecondPointWidth * 4, mPaint);}//繪制刻度private void paintScale(Canvas canvas) {mPaint.setStrokeWidth(SizeUtils.dp2px(1));int lineWidth = 0;for (int i = 0; i < 60; i++) {if (i % 5 == 0) { //整點(diǎn)mPaint.setStrokeWidth(SizeUtils.dp2px(1.5f));mPaint.setColor(mColorLong);lineWidth = 40;mPaint.setTextSize(mTextSize);String text = ((i / 5) == 0 ? 12 : (i / 5)) + "";Rect textBound = new Rect();mPaint.getTextBounds(text, 0, text.length(), textBound);mPaint.setColor(Color.BLACK);canvas.save();canvas.translate(0, -mRadius + DpToPx(5) + lineWidth + mPadding + (textBound.bottom - textBound.top) / 2);mPaint.setStyle(Paint.Style.FILL);canvas.rotate(-6 * i);canvas.drawText(text, -(textBound.right + textBound.left) / 2, -(textBound.bottom + textBound.top) / 2, mPaint);canvas.restore();} else { //非整點(diǎn)lineWidth = 30;mPaint.setColor(mColorShort);mPaint.setStrokeWidth(SizeUtils.dp2px(1));}canvas.drawLine(0, -mRadius + mPadding, 0, -mRadius + mPadding + lineWidth, mPaint);canvas.rotate(6);}}class NoDetermineSizeException extends Exception {NoDetermineSizeException(String message) {super(message);}}
}
WatcherBoard類里面需要使用到的兩個(gè)類如下:
package cn.xiayiye.custormtext;/*** 創(chuàng) 建 者:下一頁5(輕飛揚(yáng))* 創(chuàng)建時(shí)間:2018/2/26.12:28* 個(gè)人小站:http://wap.yhsh.ai(已掛)* 最新小站:http://www.iyhsh.icoc.in* 聯(lián)系作者:企鵝 13343401268* 博客地址:http://blog.csdn.net/xiayiye5* 空間名稱:XiaYiYeMap* 項(xiàng)目包名:cn.xiayiye.custormtext*/
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;/*** <pre>* author: Blankj* blog : http://blankj.com* time : 2016/08/02* desc : 尺寸相關(guān)工具類* </pre>*/
public final class SizeUtils {private SizeUtils() {throw new UnsupportedOperationException("u can't instantiate me...");}/*** dp轉(zhuǎn)px** @param dpValue dp值* @return px值*/public static int dp2px(float dpValue) {final float scale = Utils.getContext().getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}/*** px轉(zhuǎn)dp** @param pxValue px值* @return dp值*/public static int px2dp( float pxValue) {final float scale = Utils.getContext().getResources().getDisplayMetrics().density;return (int) (pxValue / scale + 0.5f);}/*** sp轉(zhuǎn)px** @param spValue sp值* @return px值*/public static int sp2px(float spValue) {final float fontScale = Utils.getContext().getResources().getDisplayMetrics().scaledDensity;return (int) (spValue * fontScale + 0.5f);}/*** px轉(zhuǎn)sp** @param pxValue px值* @return sp值*/public static int px2sp( float pxValue) {final float fontScale = Utils.getContext().getResources().getDisplayMetrics().scaledDensity;return (int) (pxValue / fontScale + 0.5f);}/*** 各種單位轉(zhuǎn)換* <p>該方法存在于TypedValue</p>** @param unit 單位* @param value 值* @param metrics DisplayMetrics* @return 轉(zhuǎn)換結(jié)果*/public static float applyDimension(int unit, float value, DisplayMetrics metrics) {switch (unit) {case TypedValue.COMPLEX_UNIT_PX:return value;case TypedValue.COMPLEX_UNIT_DIP:return value * metrics.density;case TypedValue.COMPLEX_UNIT_SP:return value * metrics.scaledDensity;case TypedValue.COMPLEX_UNIT_PT:return value * metrics.xdpi * (1.0f / 72);case TypedValue.COMPLEX_UNIT_IN:return value * metrics.xdpi;case TypedValue.COMPLEX_UNIT_MM:return value * metrics.xdpi * (1.0f / 25.4f);}return 0;}/*** 在onCreate中獲取視圖的尺寸* <p>需回調(diào)onGetSizeListener接口,在onGetSize中獲取view寬高</p>* <p>用法示例如下所示</p>* <pre>* SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() {* Override* public void onGetSize(View view) {* view.getWidth();* }* });* </pre>** @param view 視圖* @param listener 監(jiān)聽器*/public static void forceGetViewSize(final View view, final onGetSizeListener listener) {view.post(new Runnable() {@Overridepublic void run() {if (listener != null) {listener.onGetSize(view);}}});}/*** 獲取到View尺寸的監(jiān)聽*/public interface onGetSizeListener {void onGetSize(View view);}/*** 測量視圖尺寸** @param view 視圖* @return arr[0]: 視圖寬度, arr[1]: 視圖高度*/public static int[] measureView(View view) {ViewGroup.LayoutParams lp = view.getLayoutParams();if (lp == null) {lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width);int lpHeight = lp.height;int heightSpec;if (lpHeight > 0) {heightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY);} else {heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);}view.measure(widthSpec, heightSpec);return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()};}/*** 獲取測量視圖寬度** @param view 視圖* @return 視圖寬度*/public static int getMeasuredWidth(View view) {return measureView(view)[0];}/*** 獲取測量視圖高度** @param view 視圖* @return 視圖高度*/public static int getMeasuredHeight(View view) {return measureView(view)[1];}
}
package cn.xiayiye.custormtext;/*** 創(chuàng) 建 者:下一頁5(輕飛揚(yáng))* 創(chuàng)建時(shí)間:2018/2/26.12:30* 個(gè)人小站:http://wap.yhsh.ai(已掛)* 最新小站:http://www.iyhsh.icoc.in* 聯(lián)系作者:企鵝 13343401268* 博客地址:http://blog.csdn.net/xiayiye5* 空間名稱:XiaYiYeMap* 項(xiàng)目包名:cn.xiayiye.custormtext*/
import android.annotation.SuppressLint;
import android.content.Context;
//import android.support.annotation.NonNull;/*** <pre>* author: Blankj* blog : http://blankj.com* time : 16/12/08* desc : Utils初始化相關(guān)* </pre>*/
public final class Utils {@SuppressLint("StaticFieldLeak")private static Context context;private Utils() {throw new UnsupportedOperationException("u can't instantiate me...");}/*** 初始化工具類** @param context 上下文*//* public static void init(@NonNull Context context) {Utils.context = context.getApplicationContext();} */public static void init( Context context) {Utils.context = context.getApplicationContext();}/*** 獲取ApplicationContext** @return ApplicationContext*/public static Context getContext() {if (context != null) return context;throw new NullPointerException("u should init first");}
}
使用方法如下:
1.首先要在application里面進(jìn)行自定義控件的初始化:
2.然后在XML布局文件中引用此控件:
<cn.xiayiye.custormtext.WatcherBoardandroid:id="@+id/shi_zhong"android:layout_width="match_parent"android:layout_height="match_parent" />
3.在頁面調(diào)用布局即可:看效果圖,說明:截圖反應(yīng)卡頓慢,所以是5秒反應(yīng)一次,正常源碼里面是一秒走一次
如果看不明白的,可下載源碼查看:源碼下載
總結(jié)
以上是生活随笔為你收集整理的Android自定义控件之自定义时钟的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。