Android 自定義時鐘控件 時針、分針、秒針的繪製這一篇就夠了

前言

對於 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);
    }
複製代碼

重寫 onMessure() 方法

重寫 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;
        }
    }
複製代碼

配置 xml 文件

由於是自定義控件,因此逼着在這裏自定義了一個控件屬性文件,位於 /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

長度值和 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();
    }
}
複製代碼

最後運行效果

讓咱們優化下界面

到此爲止咱們的小時鐘就定義完啦,若是你們閱讀過程當中發現錯誤,歡迎評論區中指出呦~~

相關文章
相關標籤/搜索