自定義 Android 鐘表盤,這一篇就夠了

關於本文:本文原先在個人 CSDN 博客發佈(由圖片水印能發現),整理以往博客過程當中,發現當時總結的很仔細,因此將其遷移到這裏,但願對你們在自定義 View 方面,能有所幫助 💗html

引言

Android 自定義 View 應用很是普遍,最近逛 github 是偶然發現一個 Demo 感受寫的很好,我結合着這個項目的內容,給你們講講如何繪製時鐘錶盤,也算是加深下本身對自定義 View 的理解,涉及內容比較多,你們慢慢吸取。java


最後效果:

開始以前,先讓你們看看最後的效果android

在這裏插入圖片描述


如今開始

讓咱們先搭建這個 Viewc++

  1. 首先,咱們定義一個叫作 ClockView 的自定義 View ,讓它繼承自 View 類。
  2. 而後在 /res/values 目錄下,創建 attrs 文件,在裏面定義一些屬性 大體以下
<?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>

繪製外圍小時圓環的準備工做

小時圓環組成分爲外圍的圓弧和四個小時數字,因此咱們須要的東西很明確了。git

  • 咱們首先須要一個 Paint 對象,用於繪製文字,
  • 還須要另外一個 Paint 對象,用於繪製圓環。

重寫構造方法:github

/* 暗色,圓弧、刻度線、時針、漸變起始色 */
    private int mDarkColor;
    /* 小時文本字體大小 */
    private float mTextSize;
    private Paint mTextPaint;
    private Paint mCirclePaint;

    public ClockView(Context context) {
        super(context);
    }

    public ClockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0, 0);
        mDarkColor = ta.getColor(R.styleable.ClockView_clock_darkColor, Color.parseColor("#80ffffff"));
        mTextSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 14));
        ta.recycle();
        // ANTI_ALIAS_FLAG 平滑繪製 不帶磕磕絆絆
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setColor(mDarkColor);
        // 居中繪製文字
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setTextSize(mTextSize);

        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint.setColor(mDarkColor);
        // 官方:使用此樣式繪製的幾何和文本將被描邊,尊重繪畫上與筆劃相關的字段。
        // 說白了就是,不要吧這塊扇形都上色,只是把最外層的邊描下
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setStrokeWidth(mCircleStrokeWidth);// 描邊寬度

    }

別忘了重寫 onMeasure 方法,測量控件大小
關於具體的測量方法,請參考自定義 View 的文章,無非就是對 MeasureSpec 的三種 mode 類型進行分類處理罷了。編程

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getMeasureResult(widthMeasureSpec), getMeasureResult(heightMeasureSpec));
    }

    private int getMeasureResult(int measureSpec){
        int defaultSize = 800;
        int size = MeasureSpec.getSize(measureSpec);
        int mode = MeasureSpec.getMode(measureSpec);
        switch (mode){
            case MeasureSpec.UNSPECIFIED:
                return defaultSize;
            case MeasureSpec.AT_MOST:
                return Math.max(defaultSize, size);
            case MeasureSpec.EXACTLY:
                return size;
            default:
                return defaultSize;
        }
    }

開始繪製外圍圓環

咱們知道,對於繪製圓與橢圓這類圖形,常常須要先用 RectF 設置一個邊界矩形再進行繪製。若是是繪製文本則是 Rect 。canvas

因此繪製外圍圓環,首先要定義一個 RectF 變量用於繪製圓環,在定義一個 Rect 變量,用於繪製文字。ide

注 mCanvas 繪圖類是 onDraw 中的參數,咱們在 onDraw 中將它保存起來工具

// 測量文字大小
    private Rect mTextRect = new Rect();
    private RectF mCircleRectF = new RectF();
    /* 小時圓圈線條寬度 */
    private float mCircleStrokeWidth = 4;

    /**
     * 畫最外圈的時間 十二、三、六、9 文本和4段弧線
     */
    private void drawOutSideArc() {
        String[] timeList = new String[]{"12", "3", "6", "9"};
        //計算數字的高度
        mTextPaint.getTextBounds(timeList[0], 0, timeList[0].length(), mTextRect);// 計算後放回一個矩形存在 mTextRect (涉及c++原生方法,會用就行不要深究)
        mCircleRectF.set(mTextRect.width() / 2 + mCircleStrokeWidth / 2,// 畫一個外界小矩形,在矩形裏畫圓
                mTextRect.height() / 2 + mCircleStrokeWidth / 2,
                getWidth() - mTextRect.width() / 2 - mCircleStrokeWidth / 2,
                getHeight() - mTextRect.height() / 2 - mCircleStrokeWidth / 2);
        mCanvas.drawText(timeList[0], getWidth() / 2, mCircleRectF.top + mTextRect.height() / 2, mTextPaint);// 定點寫字,經過 RectF 取得邊界值,因爲是頂點在右上方寫字,因此要向下平移
        mCanvas.drawText(timeList[1], mCircleRectF.right, getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
        mCanvas.drawText(timeList[2], getWidth() / 2, mCircleRectF.bottom + mTextRect.height() / 2, mTextPaint);
        mCanvas.drawText(timeList[3], mCircleRectF.left, getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
        //畫鏈接數字的4段弧線
        for (int i = 0; i < 4; i++) {
            // 畫四個弧線 sweepAngle 弧線角度(扇形角度)
            mCanvas.drawArc(mCircleRectF, 5 + 90 * i, 80, false, mCirclePaint);
        }
    }

接着,咱們重寫 onDraw() 方法,並在 onDraw() 方法中,調用上面這個方法繪製圓環

private Canvas mCanvas;
    @Override
    protected void onDraw(Canvas canvas) {
        mCanvas = canvas;
        drawOutSideArc();
    }

運行一下看看效果

咱們看到 圓環和時間是出來了,可是這麼是個橢圓呢,在仔細檢查下咱們的代碼,在繪製過程當中,控制咱們圓環的 mCircleRectF 對象,是以整個控件大小爲邊界的,因此緣由就很明瞭了,那麼咱們只要將 mCircleRectF 對象設置成一個正方形就行。
在這裏插入圖片描述
------------------------

重寫 onSizeChanged() 方法,保證繪製的是圓

包正繪圖是圓形的前提是:

  1. 保證 RectF 切割的是正方形
  2. 那麼保證 RextF 圍成的是正方形,就要須要知道正方形四邊距離控件邊界的距離
  3. 也就是咱們須要計算四個整型變量 :1.mPaddingLeft | 2.mPaddingTop | 3.mPaddingRight |
    4.mPaddingBottom
private float mRadius;
    /* 加一個默認的padding值,爲了防止用camera旋轉時鐘時形成四周超出view大小 */
    private float mDefaultPadding;
    private float mPaddingLeft;
    private float mPaddingTop;
    private float mPaddingRight;
    private float mPaddingBottom;// 以上4值 均在 onSizechanged()中測量
    
    @Override
    protected void onSizeChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        mRadius = Math.min(l - getPaddingLeft() - getPaddingRight(),
                t - getPaddingTop() - getPaddingBottom()) / 2;// 各個指針長度
        mDefaultPadding = 0.12f * mRadius;
        mPaddingLeft = mDefaultPadding + l / 2 - mRadius + getPaddingLeft();// 鍾離左邊界距離
        mPaddingRight = mDefaultPadding + l / 2 - mRadius + getPaddingRight();// 鍾離右邊界距離
        mPaddingTop = mDefaultPadding + t / 2 - mRadius + getPaddingTop();// 鍾離上邊界距離
        mPaddingBottom = mDefaultPadding + t / 2 - mRadius + getPaddingBottom();// 鍾離下邊界距離
    }

對於圓的半徑 mRadius ,咱們就取控件長和寬中,短的那個的一半爲它的值,除此以外還有一種狀況,若是控件設置了 padding 那麼,若是知識取長寬中短的,那麼不管 padding 的值怎麼設置,控件的半徑始終都是保持長寬中短的那邊的一半不變,這樣取值使得 padding 失去了做用,也就顯得不那麼人性化了,因此真正的半徑應該是長寬中短的那邊,再減去兩個 padding 的值,以下:

mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(), h - getPaddingTop() - getPaddingBottom()) / 2;

那麼這個 mDefaultPadding 又是什麼做用呢?不如咱們將其山區看看效果:

在這裏插入圖片描述
試想一下若是咱們,沒有這個默認值,那麼用戶在沒有設置 padding 時,畫出的圓弧必然和 View 的邊界相切,圓弧相切到嗨沒啥,關鍵是圓弧上顯示時間的文字也得給截去了一半,但有了這個 mDefaultPadding 就不要懼怕這個問題。

在這裏插入圖片描述


繪製刻度線的準備

開始繪製先前,咱們先要準備下一些工具,

  1. 首先一個 Paint 對象是必不可少的,
  2. 而後爲了方便用戶使用,咱們再定義一個顏色,暴露給予設置,
  3. 最後咱們還須要一個 int 型的值,用來設定刻度線的長度
/* 刻度線長度 */
    private float mScaleLength;
    /* 刻度線畫筆 */
    private Paint mScaleLinePaint;
    /* 背景色 */
    private int mBackgroundColor;

    public ClockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0, 0);
        mBackgroundColor = ta.getColor(R.styleable.ClockView_clock_backgroundColor, Color.parseColor("#237EAD"));
        mDarkColor = ta.getColor(R.styleable.ClockView_clock_darkColor, Color.parseColor("#80ffffff"));
        mTextSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 14));
        ta.recycle();
        .
        .
        .
        mScaleLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mScaleLinePaint.setStyle(Paint.Style.STROKE);
        mScaleLinePaint.setColor(mBackgroundColor);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        .
        .
        .
        mScaleLength = 0.12f * mRadius;// 根據比例肯定刻度線長度
        mScaleLinePaint.setStrokeWidth(0.012f * mRadius);// 刻度圈的寬度
    }

開始繪製刻度線

繪製國晨反而很簡單,對於咱們來講 一小時 60min 一分鐘 60s,最好的狀況莫過於分爲 360 份,可是這樣一來,因爲手機屏幕比較小會直接致使先太密集,密集到了變成圓地步:

在這裏插入圖片描述

因此這裏,咱們將 360 度,劃分爲 200份 ,

  1. 360/200 = 1.8f
  2. 繪製時,咱們沒繪製一條邊 將 Canvas 角度旋轉 1.8f
  3. 起點:每次咱們都從畫板頂部開始,下移一個 Padding 再加上 mTextRect 的高度,也就是點鐘文字高度,以後再加上一個
    刻度線長度因爲將刻度線與圓弧分隔開來,防止它們粘在一塊兒
  4. 終點:筆起點多一個 刻度線長度便可
/**
     * 畫一圈梯度渲染的亮暗色漸變圓弧,重繪時不斷旋轉,上面蓋一圈背景色的刻度線
     */
    private void drawScaleLine() {
        mCanvas.save();
        // 畫背景色刻度線
        for (int i = 0; i < 100; i++) {
            mCanvas.drawLine(getWidth() / 2, mPaddingTop + mScaleLength + mTextRect.height() / 2,
                    getWidth() / 2, mPaddingTop + 2 * mScaleLength + mTextRect.height() / 2, mScaleLinePaint);
            mCanvas.rotate(1.8f, getWidth() / 2, getHeight() / 2);
        }
        mCanvas.restore();
    }

大功告成

項目 Demo 地址:
https://github.com/FishInWater-1999/android_view_user_defined_first.git

若是有錯歡迎在評論區指出,很是感謝~

祝你們編程愉快!

相關文章
相關標籤/搜索