天天看點

Android自定義View繪制時鐘表盤

重要:原創,轉載注明出處

trueMi-簡書

truemi-github

首先看下效果圖:

Android自定義View繪制時鐘表盤

實作步驟:

  • 繪制表盤[刻度,數字]
  • 繪制指針
  • 讓指針走起來~

具體如下:

繪制表盤:

首先需要計算出刻度的起點和終點坐标值,這裡我們通過建構兩個半徑不同的同心圓,大圓半徑減小圓半徑,就可以得到一條刻度,隻用改變角度,就可以擷取所有刻度:

Android自定義View繪制時鐘表盤
/**
     * 通過改變角度值,擷取不同角度方向的外圓一點到圓心連線過内圓一點的路徑坐标集合
     * @param x0 圓心x
     * @param y0 圓心y
     * @param outRadius 外圓半徑
     * @param innerRadius 内圓半徑
     * @param angle 角度
     * @return 傳回
     */
    private float[] getDialPaths(int x0,int y0,int outRadius,int innerRadius,int angle){
        float[] paths = new float[4];
        paths[0]  = (float) (x0 + outRadius * Math.cos(angle * Math.PI / 180));
        paths[1]  = (float) (y0 + outRadius * Math.sin(angle * Math.PI / 180));
        paths[2]  = (float) (x0 + innerRadius * Math.cos(angle * Math.PI / 180));
        paths[3]  = (float) (y0 + innerRadius * Math.sin(angle * Math.PI / 180));
        return paths;
    }
           

秒針刻度間隔360/60 = 6 度,循環繪制60次,每一次角度加6,就可以了;繪制代碼如下:

for (int i = 0; i < 60 ; i++) {
            if (i % 5 == 0){
                //擷取刻度路徑
                float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 5 / 6, -i * 6);
                canvas.drawLines(dialKdPaths,paintKd30);
                float[] dialPathsStr = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 3 / 4, -i * 6);
                canvas.drawText(strKedu[i/5],dialPathsStr[2] - 16,dialPathsStr[3] + 14,paintKd30Text);
                continue;
            }
            float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 7 / 8, -i * 6);
            canvas.drawLines(dialKdPaths,paintKdSecond);
        }
           

繪制指針和旋轉指針

這裡的重點在于對指針旋轉的了解:

Android自定義View繪制時鐘表盤

通過上圖可以看到,我們通過旋轉畫布,然後繪制指針,最後恢複畫布,進而改變了指針的指向.

具體操作過程是:

  1. 儲存已經繪制畫面
  2. 以一定角度旋轉畫布
  3. 繪制指針
  4. 恢複畫布角度

    代碼如下:以時針繪制為例

//時針繪制
        canvas.save(); //儲存之前内容
        canvas.rotate(angleHour,halfMinLength,halfMinLength); //旋轉的是畫布,進而得到指針旋轉的效果
        canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength*3/4,paintHour);
        canvas.restore(); //恢複
           

讓時間走起來

通過實時的計算時針,分針,秒針的角度,然後通知重新繪制畫面,我們就看到時間在走動.

/**
     * 更新時分秒針的角度,開始繪制
     */
    public void startRun(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (drawable){
                    try {
                        Thread.sleep(1000); // 睡1s
                        updataAngleSecond(); //更新秒針角度
                        updataAngleMinute(); //更新分針角度
                        updataAngleHour(); //更新時針角度
                        postInvalidate(); //重新繪制
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
           

完整代碼如下:

package com.truemi.dialapplication;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import java.util.Calendar;


public class DialView extends View {

    private boolean drawable = true; //是否可以繪制
    private int halfMinLength; //最小寬/高的一半長度
    private Paint paintKd30; //時針刻度線畫筆
    private Paint paintKd30Text; // 時針數字畫筆
    private Paint paintKdSecond; //秒針刻度線畫筆
    private Paint paintHour;  //時針畫筆
    private Paint paintCircleBar;//指針圓心畫筆
    private Paint paintMinute; //分針畫筆
    private Paint paintSecond; //秒針畫筆
    private float angleHour; //時針旋轉角度
    private float angleMinute; //分針旋轉角度
    private float angleSecond; //秒針旋轉角度
    private int cuurSecond; //目前秒
    private int cuurMinute; //目前分
    private int cuurHour; //目前時
    private Calendar mCalendar;
    private boolean isMorning = true; //上午/下午
    private String[] strKedu = {"3","2","1","12","11","10","9","8","7","6","5","4"};


    public DialView(Context context) {
        this(context,null);
    }

    public DialView(Context context, AttributeSet attrs) {
        this(context, attrs,-1);
    }

    public DialView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initPaint(); //初始化畫筆
        initTime(); //初始化時間

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        halfMinLength = Math.min(width,height) / 2;
        System.out.println(halfMinLength);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //表盤刻度繪制
        for (int i = 0; i < 60 ; i++) {
            if (i % 5 == 0){
                //擷取刻度路徑
                float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 5 / 6, -i * 6);
                canvas.drawLines(dialKdPaths,paintKd30);
                float[] dialPathsStr = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 3 / 4, -i * 6);
                canvas.drawText(strKedu[i/5],dialPathsStr[2] - 16,dialPathsStr[3] + 14,paintKd30Text);
                continue;
            }
            float[] dialKdPaths = getDialPaths(halfMinLength, halfMinLength, halfMinLength - 8, halfMinLength * 7 / 8, -i * 6);
            canvas.drawLines(dialKdPaths,paintKdSecond);
        }
        //指針繪制
        //時針繪制
        canvas.save(); //儲存之前内容
        canvas.rotate(angleHour,halfMinLength,halfMinLength); //旋轉的是畫布,進而得到指針旋轉的效果
        canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength*3/4,paintHour);
        canvas.restore(); //恢複
        //繪制分針
        canvas.save();
        canvas.rotate(angleMinute,halfMinLength,halfMinLength); //旋轉的是畫布,進而得到指針旋轉的效果
        canvas.drawLine(halfMinLength,halfMinLength,halfMinLength,halfMinLength/2,paintMinute);
        paintCircleBar.setColor(Color.rgb(75,75,75));
        paintCircleBar.setShadowLayer(4,4,8,Color.argb(70,40,40,40));
        canvas.drawCircle(halfMinLength,halfMinLength,24,paintCircleBar);
        canvas.restore();
        //繪制秒針
        canvas.save();
        canvas.rotate(angleSecond,halfMinLength,halfMinLength); //旋轉的是畫布,進而得到指針旋轉的效果
        canvas.drawLine(halfMinLength,halfMinLength + 40,halfMinLength,halfMinLength / 4 - 20,paintSecond);
        paintCircleBar.setColor(Color.rgb(178,34,34));
        paintCircleBar.setShadowLayer(4,4,8,Color.argb(50,80,0,0));
        canvas.drawCircle(halfMinLength,halfMinLength,12,paintCircleBar);
        canvas.restore();
    }

    /**
     * 初始化時,分,秒
     */
    private void initTime() {
        mCalendar = Calendar.getInstance();
        cuurHour = mCalendar.get(Calendar.HOUR_OF_DAY);
        cuurMinute = mCalendar.get(Calendar.MINUTE);
        cuurSecond = mCalendar.get(Calendar.SECOND);
        if (cuurHour >= 12){
            cuurHour = cuurHour - 12;
            isMorning = false;
        }else{
            isMorning = true;
        }
        angleSecond = cuurSecond * 6f;
        angleMinute = cuurMinute * 6f;
        angleHour = cuurHour * 6f * 5f;
    }

    /**
     * 更新時分秒針的角度,開始繪制
     */
    public void startRun(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (drawable){
                    try {
                        Thread.sleep(1000); // 睡1s
                        updataAngleSecond(); //更新秒針角度
                        updataAngleMinute(); //更新分針角度
                        updataAngleHour(); //更新時針角度
                        postInvalidate(); //重新繪制
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    private void updataAngleHour() {
        //更新時針角度
        angleHour = angleHour + (30f/3600);
        if (angleHour >= 360){
            angleHour = 0;
            cuurHour = 0;
        }
    }

    private void updataAngleMinute() {
        //更新分針角度
        angleMinute = angleMinute + 0.1f;
        if (angleMinute >= 360){
            angleMinute = 0;
            cuurMinute = 0;
            cuurHour += 1;
        }
    }

    private void updataAngleSecond() {
        //更新秒針角度
        angleSecond = angleSecond + 6;
        cuurSecond += 1;
        if (angleSecond >= 360){
            angleSecond = 0;
            cuurSecond = 0;
            cuurMinute += 1;
            //一分鐘同步一次本地時間
            mCalendar = Calendar.getInstance();
            cuurHour = mCalendar.get(Calendar.HOUR_OF_DAY);
            cuurMinute = mCalendar.get(Calendar.MINUTE);
            cuurSecond = mCalendar.get(Calendar.SECOND);
            if (cuurHour >= 12){
                cuurHour = cuurHour - 12;
                isMorning = false;
            }else{
                isMorning = true;
            }
            angleSecond = cuurSecond * 6f;
            angleMinute = cuurMinute * 6f;
            angleHour = cuurHour * 6f * 5f;
        }
    }
    /**
     * 停止繪制
     */
    public void stopDrawing(){
        drawable = false;
    }

    /**
     * 通過改變角度值,擷取不同角度方向的外圓一點到圓心連線過内圓一點的路徑坐标集合
     * @param x0 圓心x
     * @param y0 圓心y
     * @param outRadius 外圓半徑
     * @param innerRadius 内圓半徑
     * @param angle 角度
     * @return 傳回
     */
    private float[] getDialPaths(int x0,int y0,int outRadius,int innerRadius,int angle){
        float[] paths = new float[4];
        paths[0]  = (float) (x0 + outRadius * Math.cos(angle * Math.PI / 180));
        paths[1]  = (float) (y0 + outRadius * Math.sin(angle * Math.PI / 180));
        paths[2]  = (float) (x0 + innerRadius * Math.cos(angle * Math.PI / 180));
        paths[3]  = (float) (y0 + innerRadius * Math.sin(angle * Math.PI / 180));
        return paths;
    }

    /**
     * 初始化畫筆參數
     */
    private void initPaint() {
        paintKd30 = new Paint();
        paintKd30.setStrokeWidth(8);
        paintKd30.setColor(Color.rgb(75,75,75));
        paintKd30.setAntiAlias(true);
        paintKd30.setDither(true);
        paintKd30.setStrokeCap(Paint.Cap.ROUND);

        paintKd30Text = new Paint();
        paintKd30Text.setTextAlign(Paint.Align.LEFT); //左對齊
        paintKd30Text.setStrokeWidth(6); //設定寬度
        paintKd30Text.setTextSize(40); //文字大小
        paintKd30Text.setTypeface(Typeface.DEFAULT_BOLD); //加粗
        paintKd30Text.setColor(Color.rgb(75,75,75)); //畫筆顔色
        paintKd30Text.setAntiAlias(true); //抗鋸齒
        paintKd30Text.setDither(true); //抖動
        paintKd30Text.setStrokeCap(Paint.Cap.ROUND); //筆尖圓角
        paintKd30Text.setShadowLayer(4,2,4,Color.argb(60,90,90,90)); //陰影

        paintKdSecond = new Paint();
        paintKdSecond.setStrokeWidth(6);
        paintKdSecond.setColor(Color.rgb(75,75,75));
        paintKdSecond.setAntiAlias(true);
        paintKdSecond.setDither(true);
        paintKdSecond.setStrokeCap(Paint.Cap.ROUND);
        paintKdSecond.setShadowLayer(4,5,10,Color.argb(50,80,80,80));

        paintHour = new Paint();
        paintHour.setStrokeWidth(30);
        paintHour.setColor(Color.rgb(75,75,75));
        paintHour.setAntiAlias(true);
        paintHour.setDither(true);
        paintHour.setStrokeCap(Paint.Cap.ROUND);
        paintHour.setShadowLayer(4,5,10,Color.argb(50,80,80,80));

        paintCircleBar = new Paint();
        paintCircleBar.setStrokeWidth(6);
//        paintCircleBar.setColor(Color.rgb(178,34,34));
        paintCircleBar.setAntiAlias(true);
        paintCircleBar.setDither(true);
        paintCircleBar.setStrokeCap(Paint.Cap.ROUND);
//        paintCircleBar.setShadowLayer(4,5,10,Color.argb(100,80,80,80));

        paintMinute = new Paint();
        paintMinute.setStrokeWidth(30);
        paintMinute.setColor(Color.rgb(75,75,75));
        paintMinute.setAntiAlias(true);
        paintMinute.setDither(true);
        paintMinute.setStrokeCap(Paint.Cap.ROUND);
        paintMinute.setShadowLayer(4,5,10,Color.rgb(80,80,80));

        paintSecond = new Paint();
        paintSecond.setStrokeWidth(6);
        paintSecond.setColor(Color.rgb(180,30,30));
        paintSecond.setAntiAlias(true);
        paintSecond.setDither(true);
        paintSecond.setStrokeCap(Paint.Cap.ROUND);
        paintSecond.setShadowLayer(4,2,10,Color.argb(100,90,90,90));

    }
}
           

代碼中有比較詳細的注釋,有問題可以留言讨論哦~~~

重要:轉載注明出處