對於 Android 開發者來講,自定義 View 是繞不開的一個坎。二對一自定義 View 自定義時鐘必然是首選,那麼咱們該如何繪製自定義時鐘呢?本篇我結合 github 上一個有趣的三方庫,來給你們講講如何做出咱們的第一個時鐘html
對於全部的自定義 View 來講,構造方法、onMeasure(),onDraw() 這幾個方法都是必不可少的。,因此哦大家先打出這套模版java
public ClockView(Context context) { super(context); } public ClockView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } 複製代碼
重寫 onMeasure() 方法的本質在於配置控件大小,而配置控件大小的重點就在於配置 setMeasuredDimension(..., ...) 方法。關於具體的配置細節能夠參照:點擊查看 blog.csdn.net/qq_43377749… 這裏覺得是自定義時鐘控件,因此內容很簡單,在三種模式下分別放回三種值便可:git
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureDimension(widthMeasureSpec), measureDimension(heightMeasureSpec)); } private int measureDimension(int measureSpec) { int defaultSize = 800; int model = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); switch (model) { case MeasureSpec.EXACTLY: return size; case MeasureSpec.AT_MOST: return Math.min(size, defaultSize); case MeasureSpec.UNSPECIFIED: return defaultSize; default: return defaultSize; } } 複製代碼
由於是自定義控件,因此逼着在這裏自定義了一個控件屬性文件,位於 /res/values/attr.xml 具體內容以下:github
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ClockView"> <attr name="clock_backgroundColor" format="color" /> <attr name="clock_lightColor" format="color" /> <attr name="clock_darkColor" format="color" /> <attr name="clock_textSize" format="dimension" /> </declare-styleable> </resources> 複製代碼
如今讓咱們開始搭建時鐘,因爲是時鐘的搭建,因此咱們基本能夠分爲一下三個步驟:canvas
得到當前系統時間markdown
繪製時針ide
繪製分針工具
繪製秒針oop
首先,要繪製時鐘,必然要得到當前的時間,要不三根指針非重合在一塊兒不可,因此讓咱們先來研究下如何得到當前系統時間,這裏咱們就須要使用到一個叫作 Calendar 的工具類,Calendar 是 Android 開發中須要獲取時間時必不可少的一個工具類。優化
所須要的信息基本能夠分爲
milliSecond (毫秒,保證秒針滾動平滑不是一跳一跳)
second
minute
hour
private void getCurrentTime() { Calendar calendar = Calendar.getInstance(); float milliSecond = calendar.get(Calendar.MILLISECOND); float second = calendar.get(Calendar.SECOND) + milliSecond / 1000;// 精確到小數點後 保證圓滑 float minute = calendar.get(Calendar.MINUTE) + second / 60; float hour = calendar.get(Calendar.HOUR) + minute / 60; } 複製代碼
可是這裏有個問題,自定義 View 中,繪製時是根據某個傾斜角度進行繪製的,而非給系統一個浮點型的時間,他就會自動取繪製。因此這裏咱們還須要知道 每一個時間(分秒時,佔總時間的比重所表明的偏轉角),在這裏咱們這三個全局私有變量:
/* 時針角度 */ private float mHourDegree; /* 分針角度 */ private float mMinuteDegree; /* 秒針角度 */ private float mSecondDegree; private void getCurrentTime(){ Calendar calendar = Calendar.getInstance(); float milliSecond = calendar.get(Calendar.MILLISECOND); float second = calendar.get(Calendar.SECOND) + milliSecond / 1000; float minute = calendar.get(Calendar.MINUTE) + second / 60; float hour = calendar.get(Calendar.HOUR) + minute / 60; mSecondDegree = second / 60 * 360; mMinuteDegree = minute / 60 * 360; mHourDegree = hour / 60 * 360; } 複製代碼
最後別忘了在 onDraw() 中調用
爲了區別於時針分針單一的矩形,這裏的秒針咱們用一個三角尖代替:
既然是繪製圖像,自定義畫筆就是必不可少的
而後,畫筆是用於上色的,因此咱們還須要一個 Path 類對象將這個小三角的邊界畫出來
因爲繪製是在成員方法中進行,因此咱們須要定一個 Canvas 對象,來保存 onDraw() 中因爲繪製視圖的 Canvas
除此以外,秒針是有長度的,因此咱們須要一個整型長度變量
最後,咱們還須要一個整型變量來存儲顏色值,顏色值應該從咱們先前定義的 xml 文件的屬性中獲取。
因爲時間是須要反覆更新的,因此 onDraw() 方法也是要被反覆調用的。這就使得 Paint 等變量不能再其中定義,而須要在構造方法中定義,不然不免有內存溢出的風險。
/* 亮色,用於分針、秒針、漸變終止色 */ private int mLightColor; /* 秒針畫筆 */ private Paint mSecondHandPaint; public ClockView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0, 0); mLightColor = ta.getColor(R.styleable.ClockView_clock_lightColor, Color.parseColor("#ffffff")); ta.recycle(); mSecondHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mSecondHandPaint.setStyle(Paint.Style.FILL); mSecondHandPaint.setColor(mLightColor); } 複製代碼
長度值和 Path 的定義和 Paint 同樣,不適合在 onDraw() 中,建議你們在 onSizeChanged 中定義,這個方法的提供了測量長度的各個形參。
/* 加一個默認的padding值,爲了防止用camera旋轉時鐘時形成四周超出view大小 */ private float mDefaultPadding; private float mPaddingTop; /* 時鐘半徑,不包括padding值 */ private float mRadius; @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(), h - getPaddingTop() - getPaddingBottom()) / 2;// 各個指針長度 mDefaultPadding = 0.12f * mRadius; mPaddingTop = mDefaultPadding + h / 2 - mRadius + getPaddingTop();// 鍾離上邊界距離 } 複製代碼
這裏把繪製方法命名爲:drawSecondNeedle() 首先咱們須要得到 Canvas 參數
/* 秒針路徑 */ private Path mSecondHandPath = new Path(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mCanvas = canvas; getCurrentTime(); drawSecondNeedle(); invalidate(); } 複製代碼
根據這個參數咱們開始繪製秒針:
首先繪製畫筆的 Style 設爲 FILL 填充
定義一個 Path 對象因爲繪製
調用 Path 對象的 moveTo 方法設定繪製起點
調用 lineTo 方法,繪製線條
調用 Canvas 的 close 方法將終點與起點連線造成封閉圖形
private void drawSecondNeedle() { mCanvas.save();// ❑ save:用來保存Canvas的狀態。save以後,能夠調用Canvas的平移、放縮、旋轉、錯切、裁剪等操做。 mCanvas.rotate(mSecondDegree, getWidth() / 2, getHeight() / 2);// 設置指針位置 mSecondHandPath.reset(); float offset = mPaddingTop; mSecondHandPath.moveTo(getWidth() / 2, offset + 0.26f * mRadius);// 這三行繪製三角尖 mSecondHandPath.lineTo(getWidth() / 2 - 0.05f * mRadius, offset + 0.34f * mRadius); mSecondHandPath.lineTo(getWidth() / 2 + 0.05f * mRadius, offset + 0.34f * mRadius); mSecondHandPath.close(); mSecondHandPaint.setColor(mLightColor); mCanvas.drawPath(mSecondHandPath, mSecondHandPaint); mCanvas.restore();// ❑ restore:用來恢復Canvas以前保存的狀態。防止save後對Canvas執行的操做對後續的繪製有影響。 } 複製代碼
要繪製分針首先得有如下準備
一個用於繪製分針的 Path 對象
一個用於繪製中心軸圓圈的 RectF 對象
一個用於畫筆對象
/* 分針路徑 */ private Path mMinuteHandPath = new Path(); /* 分針畫筆 */ private Paint mMinuteHandPaint; /* 小時圓圈的外接矩形 */ private RectF mCircleRectF = new RectF(); /** * 繪製分針 */ private void drawMinuteNeedle() { mCanvas.save(); mCanvas.rotate(mMinuteDegree, getWidth() / 2, getHeight() / 2); mMinuteHandPath.reset(); float offset = mPaddingTop ; mMinuteHandPath.moveTo(getWidth() / 2 - 0.01f * mRadius, getHeight() / 2 - 0.03f * mRadius); mMinuteHandPath.lineTo(getWidth() / 2 - 0.008f * mRadius, offset + 0.365f * mRadius); mMinuteHandPath.quadTo(getWidth() / 2, offset + 0.345f * mRadius, getWidth() / 2 + 0.008f * mRadius, offset + 0.365f * mRadius); mMinuteHandPath.lineTo(getWidth() / 2 + 0.01f * mRadius, getHeight() / 2 - 0.03f * mRadius); mMinuteHandPath.close(); mMinuteHandPaint.setStyle(Paint.Style.FILL); mCanvas.drawPath(mMinuteHandPath, mMinuteHandPaint); mCircleRectF.set(getWidth() / 2 - 0.03f * mRadius, getHeight() / 2 - 0.03f * mRadius,//繪製指針軸的小圓圈 getWidth() / 2 + 0.03f * mRadius, getHeight() / 2 + 0.03f * mRadius); mMinuteHandPaint.setStyle(Paint.Style.STROKE); mMinuteHandPaint.setStrokeWidth(0.02f * mRadius); mCanvas.drawArc(mCircleRectF, 0, 360, false, mMinuteHandPaint); mCanvas.restore(); } 複製代碼
首先咱們根據以前計算得到的角度旋轉畫筆到當前要繪製的時間
而後咱們繪製分針,繪製方法很簡單,首先咱們將畫筆移到 View 中心篇左的地方
而後用 lineTo 繪製一條直線
接着用 quadTo 繪製一條曲線到右邊對稱點
再接着 用 lineTo 繪製一條直線到中心篇右
最後調用 close 方法閉合圖形便可
至於繪製圓心軸的方法就不說了 就是最基本的繪製圓的方法,先設定 RectF 對象,在調用 fraeArc 方法繪製便可
繪製是真的過程與繪製分針如出一轍,因爲軸心圓的 RectF 能夠直接調用以前繪製分針用到的,因此甚至是更簡單些
/* 時針路徑 */ private Path mHourHandPath = new Path(); /* 時針畫筆 */ private Paint mHourHandPaint; /** * 繪製時針 */ private void drawHourHand() { mCanvas.save(); mCanvas.rotate(mHourDegree, getWidth() / 2, getHeight() / 2); mHourHandPath.reset(); float offset = mPaddingTop; mHourHandPath.moveTo(getWidth() / 2 - 0.018f * mRadius, getHeight() / 2 - 0.03f * mRadius); mHourHandPath.lineTo(getWidth() / 2 - 0.009f * mRadius, offset + 0.48f * mRadius); mHourHandPath.quadTo(getWidth() / 2, offset + 0.46f * mRadius, getWidth() / 2 + 0.009f * mRadius, offset + 0.48f * mRadius); mHourHandPath.lineTo(getWidth() / 2 + 0.018f * mRadius, getHeight() / 2 - 0.03f * mRadius); mHourHandPath.close(); mHourHandPaint.setStyle(Paint.Style.FILL); mCanvas.drawPath(mHourHandPath, mHourHandPaint); mCircleRectF.set(getWidth() / 2 - 0.03f * mRadius, getHeight() / 2 - 0.03f * mRadius, getWidth() / 2 + 0.03f * mRadius, getHeight() / 2 + 0.03f * mRadius); mHourHandPaint.setStyle(Paint.Style.STROKE); mHourHandPaint.setStrokeWidth(0.01f * mRadius); mCanvas.drawArc(mCircleRectF, 0, 360, false, mHourHandPaint); mCanvas.restore(); } } 複製代碼
到此爲止咱們的小時鐘就定義完啦,若是你們閱讀過程當中發現錯誤,歡迎評論區中指出呦~~