自定义View——闹钟

梦想与她 提交于 2020-01-16 20:47:26

我们今天来自定义一个闹钟。效果如下:
在这里插入图片描述

第一步:自定义属性

在文件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>

效果如前所示。

谢谢阅读!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!