天天看點

自定義View——鬧鐘

我們今天來自定義一個鬧鐘。效果如下:

自定義View——鬧鐘

具體代碼請參考:​​Demo​​

第一步:自定義屬性

在檔案app/src/main/res/values/attrs.xml中加入自定義屬性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ClockView">
        <attr name="circle_color" format="color"/>
        <attr name="circle_scale_color" format="color"/>
        <attr name="sec_point_color" format="color"/>
        <attr name="min_point_color" format="color"/>
        <attr name="hour_point_color" format="color"/>
        <attr name="dot_color" format="color"/>
    </declare-styleable>
    ...
</resources>      

第二步:重寫onMeasure 、onLayout、onDraw方法

重寫onMeasure主要是為了解決,當wrap_content時,視圖會占滿整個父布局,是以要設定一個預設值。

重寫onLayout是因為遵守良好的習慣。因為onMeasure可能會重複調用多次,那麼視圖的寬高就可能在最後一次才能确定下來。當然這種情況更可能出現在繼承ViewGroup中的自定義視圖中。我們本例是繼承View,是以一般不會出現上述情況。onLayout中一定能拿到視圖的寬高而且是确定下來的。

onDraw方法一般都需要重寫。

package com.wong.clock;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Calendar;

public class ClockView extends View {

    /*畫筆*/
    private Paint mPaintCircle = new Paint();
    private Paint mPaintDot = new Paint();
    private Paint mPaintScale = new Paint();
    private Paint mPaintHour = new Paint();
    private Paint mPaintMin = new Paint();
    private Paint mPaintSec = new Paint();
    private int width = 0;
    private int height = 0;
    private int circleColor = Color.WHITE;
    private int scaleColor = Color.GRAY;
    private int dotColor = Color.BLACK;
    private int hourPointColor = Color.BLACK;
    private int minPointColor = Color.BLACK;
    private int secPointColor = Color.RED;
    private float hourPointWidth = 12f;
    private float minPointWidth = 8f;
    private float secPointWidth = 2f;
    private float dotWidth = 0;

    public void setCircleColor(int circleColor){
        this.circleColor = circleColor;
    }

    public void setDotColor(int dotColor) {
        this.dotColor = dotColor;
    }

    public void setHourPointColor(int hourPointColor) {
        this.hourPointColor = hourPointColor;
    }

    public void setMinPointColor(int minPointColor) {
        this.minPointColor = minPointColor;
    }

    public void setSecPointColor(int secPointColor) {
        this.secPointColor = secPointColor;
    }

    public void setHourPointWidth(float hourPointWidth) {
        this.hourPointWidth = hourPointWidth;
    }

    public void setMinPointWidth(float minPointWidth) {
        this.minPointWidth = minPointWidth;
    }

    public void setSecPointWidth(float secPointWidth) {
        this.secPointWidth = secPointWidth;
    }

    public ClockView(Context context) {
        super(context);
        init(context,null);
    }

    public ClockView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs);
    }

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

    public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context,attrs);
    }
    private void init(@NonNull Context context,AttributeSet attrs){


        /*初始化xml屬性的值*/
        if(attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.ClockView);
            circleColor = typedArray.getColor(R.styleable.ClockView_circle_color,circleColor);
            scaleColor = typedArray.getColor(R.styleable.ClockView_circle_scale_color,scaleColor);
            dotColor = typedArray.getColor(R.styleable.ClockView_dot_color,dotColor);
            hourPointColor = typedArray.getColor(R.styleable.ClockView_hour_point_color,hourPointColor);
            minPointColor = typedArray.getColor(R.styleable.ClockView_min_point_color,minPointColor);
            secPointColor = typedArray.getColor(R.styleable.ClockView_sec_point_color,secPointColor);
            hourPointWidth = typedArray.getDimension(R.styleable.ClockView_hour_point_width,hourPointWidth);
            minPointWidth = typedArray.getDimension(R.styleable.ClockView_min_point_width,minPointWidth);
            secPointWidth = typedArray.getDimension(R.styleable.ClockView_sec_point_width,secPointWidth);
            dotWidth = typedArray.getDimension(R.styleable.ClockView_dot_width,dotWidth);
            typedArray.recycle();
        }

        /*表盤*/
        mPaintCircle.setStyle(Paint.Style.FILL);
        mPaintCircle.setAntiAlias(true);
        mPaintCircle.setColor(circleColor);
        /*刻度*/
        mPaintScale.setStyle(Paint.Style.FILL);
        mPaintScale.setAntiAlias(true);
        mPaintScale.setColor(scaleColor);
        /*圓心的圓*/
        mPaintDot.setStyle(Paint.Style.FILL);
        mPaintDot.setAntiAlias(true);
        mPaintDot.setColor(dotColor);
        /*hour*/
        mPaintHour.setAntiAlias(true);
        mPaintHour.setStrokeWidth(hourPointWidth);
        mPaintHour.setStyle(Paint.Style.FILL);
        mPaintHour.setColor(hourPointColor);
        /*min*/
        mPaintMin.setAntiAlias(true);
        mPaintMin.setStrokeWidth(minPointWidth);
        mPaintMin.setStyle(Paint.Style.FILL);
        mPaintMin.setColor(minPointColor);
        /*sec*/
        mPaintSec.setAntiAlias(true);
        mPaintSec.setStrokeWidth(secPointWidth);
        mPaintSec.setStyle(Paint.Style.FILL);
        mPaintSec.setColor(secPointColor);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

         int widthW = widthSize - getPaddingLeft()-getPaddingRight();
         int heightH = heightSize - getPaddingTop() - getPaddingBottom();


        switch (widthMode){
            case MeasureSpec.EXACTLY:
            case MeasureSpec.AT_MOST:
                if(getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) {
                    width = widthW;
                }else if(getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT){
                    width  = (int)getResources().getDimension(R.dimen.clock_view_width);
                }else{
                    width = widthW;
                }
                break;
                case MeasureSpec.UNSPECIFIED:
                    width = widthW;
                    break;
        }
        switch (heightMode){
            case MeasureSpec.EXACTLY:
            case MeasureSpec.AT_MOST:
                if(getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) {
                    height = heightH;
                }else if(getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT){
                    height = (int)getResources().getDimension(R.dimen.clock_view_height);
                }else{
                    height = heightH;
                }
                break;
            case MeasureSpec.UNSPECIFIED:
                height = heightH;
                break;
        }
        setMeasuredDimension(width,height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /*設定畫布的顔色*/
        canvas.drawColor(Color.WHITE);
        int radius = Math.min(width,height)/2;
        /*畫刻盤*/
        canvas.drawCircle(width/2,height/2,radius,mPaintCircle);

        /*繪制12個點,代表12小時*/
        float scaleRadius = radius / 24;
        float dotY = (float) height/2-radius+5*scaleRadius/4;
        for(int i = 0; i < 12; i++){
            canvas.drawCircle(width/2, dotY, scaleRadius, mPaintScale);
            canvas.rotate(30f,width/2,height/2);
        }
        /*繪制時針、分針、秒針*/
        Calendar calendar = Calendar.getInstance();
        float hour = calendar.get(Calendar.HOUR);
        float min = calendar.get(Calendar.MINUTE);
        float sec = calendar.get(Calendar.SECOND);
        /*時針轉過的角度*/
        float angleHour = hour * (float) (360 / 12) + min/60*30;
        /*分針轉過的角度*/
        float angleMin = min * 6;
        /*秒針轉過的角度*/
        float angleSec = sec * 6;

        /*繪制時針*/
        float hourY = (float) height/2-radius+(float) radius / 2;
        canvas.save();
        canvas.rotate(angleHour,width/2,height/2);
        canvas.drawLine(width/2,height/2,width/2,hourY,mPaintHour);
        canvas.restore();

        /*繪制分針*/
        float minY = (float) height/2-radius+(float) radius / 3;
        canvas.save();
        canvas.rotate(angleMin,width/2,height/2);
        canvas.drawLine(width/2,height/2,width/2,minY,mPaintMin);
        canvas.restore();

        /*繪制分針*/
        float secY = (float) height/2-radius+(float) radius / 5;
        canvas.save();
        canvas.rotate(angleSec,width/2,height/2);
        canvas.drawLine(width/2,height/2,width/2,secY,mPaintSec);
        canvas.restore();

        /*在圓心畫個圓蓋住三條指針的連接配接處*/
        float dotRadius = Math.max(dotWidth,(float) 0.05*radius);
        canvas.drawCircle(width/2,height/2,dotRadius,mPaintDot);

        /*每隔1秒就重新整理*/
        postDelayed(new Runnable() {
            @Override
            public void run() {
                invalidate();
            }
        },1000);

    }
}      

代碼的具體含義,請看代碼中的注釋。

第三步:在布局檔案中使用鬧鐘視圖

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.wong.clock.ClockView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:background="@color/colorPrimaryDark"
        app:circle_color="@color/colorPrimaryDark"
        app:circle_scale_color="@android:color/white"
        app:dot_color="@android:color/white"
        app:hour_point_color="@android:color/white"
        app:min_point_color="@android:color/white"
        app:sec_point_color="@android:color/holo_red_dark"
        app:sec_point_width="3dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>