Android自定義控件時鐘、鐘錶AlarmClockView,仿華爲手機世界時鐘控件效果

轉載請標明出處:https://blog.csdn.net/m0_38074457/article/details/85790550,本文出自【陳少華的博客】

一、效果圖

效果圖

二、控件結構

 三、代碼實現

1、attrs.xml中添加自定義控件的屬性

<declare-styleable name="AlarmClockView">
        <attr name="outerCircleColor" format="reference|color" />
        <attr name="innerCircleColor" format="reference|color" />
        <attr name="secondHandColor" format="reference|color" />
        <attr name="minuteHandColor" format="reference|color" />
        <attr name="hourHandColor" format="reference|color" />
        <attr name="minuteScaleColor" format="reference|color" />
        <attr name="scaleColor" format="reference|color" />
        <attr name="dateValueColor" format="reference|color" />
        <attr name="isShowTime" format="boolean" />
        <attr name="proportion" format="float" />
    </declare-styleable>

2、創建自定義控件AlarmClockView繼承View(請結合具體代碼來看)

1)構造方法中通過initView獲取初始化值。

2)onSizeChanged方法中獲取控件的寬高,並設置時鐘的中心點座標、半徑等信息。

3)onDraw方法中根據上圖繪製流程對時鐘進行繪製。

4)調用start方法並設置監聽啓動鬧鐘,通過handler每隔1秒獲取當前時間,並刷新控件。

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import java.util.Calendar;

/**
 * Created by HARRY on 2019/1/4 0004.
 */

public class AlarmClockView extends View {

    /**
     * 秒針顏色
     */
    private int mSecondHandColor;
    /**
     * 分針顏色
     */
    private int mMinuteHandColor;
    /**
     * 時針顏色
     */
    private int mHourHandColor;
    /**
     * 分鐘刻度顏色
     */
    private int mMinuteScaleColor;
    /**
     * 當分鐘是5的倍數時刻度的顏色
     */
    private int mPointScaleColor;
    /**
     * 時鐘底部時間文本顏色
     */
    private int mDateValueColor;
    /**
     * 時鐘寬度
     */
    private int mClockWid;
    /**
     * 時鐘最外層圓半徑
     */
    private int mOuterRadius;
    /**
     * 時鐘圓心x
     */
    private int mCenterX;
    /**
     * 時鐘圓心y
     */
    private int mCenterY;
    /**
     * 控件寬
     */
    private int mWid;
    /**
     * 控件高
     */
    private int mHei;
    private Paint mPaint = new Paint();
    /**
     * 最外層圓顏色
     */
    private int mOuterCircleColor;
    /**
     * 內層圓顏色
     */
    private int mInnerCircleColor;
    /**
     * 內層半徑
     */
    private int mInnerRadius;
    /**
     * 內外圓的間距
     */
    private int mSpace = 10;
    /**
     * 現在的時間小時
     */
    private int mHour;
    /**
     * 現在的時間分鐘
     */
    private int mMinute;
    /**
     * 現在的時間秒
     */
    private int mSecond;
    /**
     * 時鐘上刻度值的高度
     */
    private int mScaleValueHei;
    private String[] arr = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
    /**
     * 現在的時間天
     */
    private int mDay;
    /**
     * 現在的時間周幾
     */
    private int mWeek;
    /**
     * 現在的時間月
     */
    private int mMonth;
    /**
     * 現在的時間年
     */
    private int mYear;
    /**
     * 是否顯示時鐘底部的時間文本
     */
    private boolean mIsShowTime;
    /**
     * 真實的周幾
     */
    private String mWeekStr;
    /**
     * 時間監聽
     */
    private TimeChangeListener listener;
    /**
     * 時鐘佔空間整體的比例
     */
    private float mProportion;

    /**
     * handler用來處理定時任務,沒隔一秒刷新一次
     */
    private Handler mHandler = new Handler();
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            mHandler.postDelayed(this, 1000);
            initCurrentTime();
        }
    };

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

    public AlarmClockView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AlarmClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public AlarmClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initView(context, attrs);
    }

    private void initView(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AlarmClockView);
        if (array != null) {
            mOuterCircleColor = array.getColor(R.styleable.AlarmClockView_outerCircleColor, getResources().getColor(R.color.gray));
            mInnerCircleColor = array.getColor(R.styleable.AlarmClockView_innerCircleColor, getResources().getColor(R.color.grayInner));
            mSecondHandColor = array.getColor(R.styleable.AlarmClockView_secondHandColor, getResources().getColor(R.color.green));
            mMinuteHandColor = array.getColor(R.styleable.AlarmClockView_minuteHandColor, getResources().getColor(R.color.black));
            mHourHandColor = array.getColor(R.styleable.AlarmClockView_hourHandColor, getResources().getColor(R.color.black));
            mMinuteScaleColor = array.getColor(R.styleable.AlarmClockView_minuteScaleColor, getResources().getColor(R.color.black));
            mPointScaleColor = array.getColor(R.styleable.AlarmClockView_scaleColor, getResources().getColor(R.color.black));
            mDateValueColor = array.getColor(R.styleable.AlarmClockView_dateValueColor, getResources().getColor(R.color.black));
            mIsShowTime = array.getBoolean(R.styleable.AlarmClockView_isShowTime, true);
            mProportion = array.getFloat(R.styleable.AlarmClockView_proportion, (float) 0.75);
            if (mProportion > 1 || mProportion < 0) {
                mProportion = (float) 0.75;
            }

            array.recycle();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWid = w;
        mHei = h;
        //使鬧鐘的寬爲控件寬的mProportion;
        mClockWid = (int) (w * mProportion);
        mOuterRadius = mClockWid / 2;
        mInnerRadius = mOuterRadius - mSpace;
        mCenterX = w / 2;
        mCenterY = mCenterX;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //設置整體控件的背景爲白色背景
        mPaint.setColor(Color.WHITE);
        canvas.drawRect(0, 0, mWid, mHei, mPaint);

        //畫外層圓
        drawOuterCircle(canvas);

        //畫內層圓
        drawInnerCircle(canvas);

        //畫刻度
        drawTickMark(canvas);

        //畫刻度值
        drawScaleValue(canvas);

        //畫針
        drawHand(canvas);

        //畫現在時間顯示
        if (mIsShowTime) {
            drawCurrentTime(canvas);
        }
    }

    /**
     * 畫時鐘底部的時間文本
     *
     * @param canvas
     */
    private void drawCurrentTime(Canvas canvas) {
        mPaint.setColor(mDateValueColor);
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(40);

        //使當前時間文本正好在時鐘底部距離有2 * mSpace的位置
        Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
        int baseLineY = mCenterY + mOuterRadius - fm.top + 2 * mSpace;

        String time = "" + mYear + "年" + (mMonth + 1) + "月" + mDay + "日" + mWeekStr + mHour + "點" + mMinute + "分" + mSecond + "秒";
        canvas.drawText(time, mCenterX, baseLineY, mPaint);
    }

    /**
     * 畫時鐘內的針
     *
     * @param canvas
     */
    private void drawHand(Canvas canvas) {
        //畫時針
        canvas.save();
        int hourWid = 16;
        mPaint.setColor(mHourHandColor);
        mPaint.setStrokeWidth(hourWid);

        for (int i = 1; i <= 12; i++) {
            canvas.rotate(30, mCenterX, mCenterY);
            if (i == mHour) {
                //計算時針的偏移量
                int offset = (int) (((float) mMinute / (float) 60) * (float) 30);
                canvas.rotate(offset, mCenterX, mCenterY);
                RectF rectF = new RectF(mCenterX - hourWid / 2, mCenterY - mInnerRadius + mScaleValueHei + 3 * mSpace, mCenterX + hourWid / 2, mCenterY);
                canvas.drawRoundRect(rectF, hourWid / 2, hourWid / 2, mPaint);
                break;
            }
        }
        canvas.restore();

        //畫分針
        canvas.save();
        int minuteWid = 10;
        mPaint.setColor(mMinuteHandColor);
        mPaint.setStrokeWidth(10);

        for (int i = 0; i < 60; i++) {
            if (i == mMinute) {
                //計算分針的偏移量
                int offset = (int) ((float) mSecond / (float) 60 * (float) 6);
                canvas.rotate(offset, mCenterX, mCenterY);
                RectF rectF = new RectF(mCenterX - minuteWid / 2, mCenterY - mInnerRadius + 3 * mSpace, mCenterX + minuteWid / 2, mCenterY);
                canvas.drawRoundRect(rectF, minuteWid / 2, minuteWid / 2, mPaint);
                break;
            } else {
                canvas.rotate(6, mCenterX, mCenterY);
            }
        }
        canvas.restore();

        //畫秒針
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(3);

        canvas.drawCircle(mCenterX, mCenterY, mSpace, mPaint);

        for (int i = 0; i < 60; i++) {
            if (i == mSecond) {
                canvas.drawLine(mCenterX, mCenterY + 3 * mSpace, mCenterX, mCenterY - mInnerRadius + mSpace, mPaint);
                break;
            } else {
                canvas.rotate(6, mCenterX, mCenterY);
            }
        }
        canvas.restore();

    }

    /**
     * 畫時鐘內的刻度值
     *
     * @param canvas
     */
    private void drawScaleValue(Canvas canvas) {
        mPaint.setColor(mPointScaleColor);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(5);
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(30);

        //計算刻度值的文本高度
        Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
        mScaleValueHei = fm.bottom - fm.top;

        for (int i = 0; i < 12; i++) {
            String degree = (i + 1) + "";
            float[] temp = calculatePoint((i + 1) * 30, mInnerRadius - mSpace * 4 - mPaint.getTextSize() / 2);
            canvas.drawText(degree, temp[2] + mCenterX, mCenterY + temp[3] + mPaint.getTextSize() / 2, mPaint);
        }
    }

    /**
     * 計算線段的起始座標
     *
     * @param angle
     * @param length
     * @return
     */
    private float[] calculatePoint(float angle, float length) {
        int POINT_BACK_LENGTH = 1;
        float[] points = new float[4];
        if (angle <= 90f) {
            points[0] = -(float) Math.sin(angle * Math.PI / 180) * POINT_BACK_LENGTH;
            points[1] = (float) Math.cos(angle * Math.PI / 180) * POINT_BACK_LENGTH;
            points[2] = (float) Math.sin(angle * Math.PI / 180) * length;
            points[3] = -(float) Math.cos(angle * Math.PI / 180) * length;
        } else if (angle <= 180f) {
            points[0] = -(float) Math.cos((angle - 90) * Math.PI / 180) * POINT_BACK_LENGTH;
            points[1] = -(float) Math.sin((angle - 90) * Math.PI / 180) * POINT_BACK_LENGTH;
            points[2] = (float) Math.cos((angle - 90) * Math.PI / 180) * length;
            points[3] = (float) Math.sin((angle - 90) * Math.PI / 180) * length;
        } else if (angle <= 270f) {
            points[0] = (float) Math.sin((angle - 180) * Math.PI / 180) * POINT_BACK_LENGTH;
            points[1] = -(float) Math.cos((angle - 180) * Math.PI / 180) * POINT_BACK_LENGTH;
            points[2] = -(float) Math.sin((angle - 180) * Math.PI / 180) * length;
            points[3] = (float) Math.cos((angle - 180) * Math.PI / 180) * length;
        } else if (angle <= 360f) {
            points[0] = (float) Math.cos((angle - 270) * Math.PI / 180) * POINT_BACK_LENGTH;
            points[1] = (float) Math.sin((angle - 270) * Math.PI / 180) * POINT_BACK_LENGTH;
            points[2] = -(float) Math.cos((angle - 270) * Math.PI / 180) * length;
            points[3] = -(float) Math.sin((angle - 270) * Math.PI / 180) * length;
        }
        return points;
    }

    /**
     * 畫時鐘的刻度線
     *
     * @param canvas
     */
    private void drawTickMark(Canvas canvas) {
        canvas.save();

        for (int i = 0; i < 60; i++) {
            if (i % 5 == 0) {
                mPaint.setColor(mPointScaleColor);
                mPaint.setStyle(Paint.Style.FILL);
                mPaint.setStrokeWidth(5);
                mPaint.setAntiAlias(true);
                mPaint.setTextAlign(Paint.Align.CENTER);
                mPaint.setTextSize(30);

                canvas.drawLine(mCenterX, mSpace * 2 + mCenterY - mOuterRadius, mCenterX, mSpace * 4 + mCenterY - mOuterRadius, mPaint);
            } else {
                mPaint.setColor(mMinuteScaleColor);
                mPaint.setStyle(Paint.Style.FILL);
                mPaint.setStrokeWidth(2);
                mPaint.setAntiAlias(true);

                canvas.drawLine(mCenterX, mSpace * 2 + mCenterY - mOuterRadius, mCenterX, mSpace * 3 + mCenterY - mOuterRadius, mPaint);
            }

            canvas.rotate(6, mCenterX, mCenterY);
        }

        canvas.restore();
    }

    /**
     * 畫內圓
     *
     * @param canvas
     */
    private void drawInnerCircle(Canvas canvas) {
        mPaint.setColor(mInnerCircleColor);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(2);

        canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mPaint);

        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);

        canvas.drawCircle(mCenterX, mCenterY, mInnerRadius - mSpace, mPaint);
    }

    /**
     * 畫外圓
     *
     * @param canvas
     */
    private void drawOuterCircle(Canvas canvas) {
        mPaint.setColor(mOuterCircleColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(2);

        canvas.drawCircle(mCenterX, mCenterY, mOuterRadius, mPaint);
    }

    /**
     * 獲取當前時間
     */
    public void initCurrentTime() {
        Calendar mCalendar = Calendar.getInstance();
        //因爲獲取的時間總是晚一秒,這裏加上這一秒
        mCalendar.add(Calendar.SECOND, 1);
        mYear = mCalendar.get(Calendar.YEAR);
        mMonth = mCalendar.get(Calendar.MONTH);
        mDay = mCalendar.get(Calendar.DAY_OF_MONTH);
        mWeek = mCalendar.get(Calendar.DAY_OF_WEEK);
        mHour = mCalendar.get(Calendar.HOUR);
        mMinute = mCalendar.get(Calendar.MINUTE);
        mSecond = mCalendar.get(Calendar.SECOND);
        Calendar calendar = Calendar.getInstance();
        //1.數組下標從0開始;2.老外的第一天是從星期日開始的
        mWeekStr = arr[calendar.get(calendar.DAY_OF_WEEK) - 1];

        System.out.println("現在時間:小時:" + mHour + ",分鐘:" + mMinute + ",秒:" + mSecond);

        if (listener != null) {
            listener.onTimeChange(mCalendar);
        }
        invalidate();
    }

    /**
     * 運行鬧鐘
     *
     * @param listener
     */
    public void start(TimeChangeListener listener) {
        this.listener = listener;
        mHandler.postDelayed(runnable, 1000);
        initCurrentTime();
    }

    /**
     * 運行鬧鐘
     */
    public void start() {
        mHandler.postDelayed(runnable, 1000);
        initCurrentTime();
    }

    /**
     * 停止鬧鐘
     */
    public void stop() {
        mHandler.removeCallbacks(runnable);
    }
}
import java.util.Calendar;

/**
 * Created by HARRY on 2019/1/4 0004.
 */

public interface TimeChangeListener {
    void onTimeChange(Calendar calendar);
}

三、項目中如何引用

步驟1.將JitPack存儲庫添加到構建文件中

項目的根build.gradle中添加以下代碼:

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}

步驟2.build.gradle添加依賴項

dependencies {
    implementation 'com.github.hnsycsxhzcsh:AlarmClockView:v1.2'
}

步驟3. 佈局中引用控件

<com.alarmclockview.AlarmClockView
        android:id="@+id/clock"
        app:proportion="0.6"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

步驟4. activity中調用start啓動鬧鐘並添加監聽

mClock = findViewById(R.id.clock);
        //運行鬧鐘
        mClock.start(new TimeChangeListener() {
            @Override
            public void onTimeChange(Calendar calendar) {
                //根據calendar獲取當前時間

            }
        });

備註:

可以在github上下載我的項目:https://github.com/hnsycsxhzcsh/AlarmClockView,如果我的博客對你有幫助的話,歡迎博客點贊支持,並在github右上角star支持!