Android自定义圆环计时器——史上最详细篇

自定义的圆环计时器提供了以下属性
<declare-styleable name="TimerCircle">
          <!--圆环的宽度-->
        <attr name="width"  format="dimension"></attr>
          <!--内圆的颜色-->
        <attr name="circleColor" format="color"></attr>
          <!--圆环的颜色-->
        <attr name="ringColor" format="color"></attr>
          <!--倒计时时间-->
        <attr name="maxTime" format="integer"></attr>
          <!--圆环的路径颜色-->
        <attr name="path" format="color"></attr>
          <!--倒计时字体颜色-->
        <attr name="textColor" format="color"></attr>
          <!--倒计时字体大小-->
        <attr name="textSize" format="dimension"></attr>
      </declare-styleable>

代码与注释

package com.example.view;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

import androidx.annotation.Nullable;

import com.example.binder.R;

public class TimerCircle extends View {
    //圆环画笔
    private Paint mPaint;
    //圆画笔
    private Paint nPaint;

    //1.圆的颜色
    private int circle_color;

    //2.圆环颜色
    private int ring_color;

    //3.圆环大小对应着画笔的宽度
    private int ring_width;

    //4.指定控件宽度和长度
    private int width;
    private int height;

    //50.字体颜色
    private int text_color;
    //51.字体大小
    private int text_size;
    //52.路径颜色
    private int path_color;
    //5.通过宽度和高度计算得出圆的半径
    private int radius;

    //6.指定动画的当前值,比如指定动画从0-10000。
    private int current_value;

    //7.进行到x秒时,对应的圆弧弧度为 x/ * 360.f x可以currentValue得出 currentValue/1000代表秒
    private float  angle_value;//圆弧角度

    //8.通过valueAnimator我们可以获得currentValue
    private ValueAnimator animator;

    //9.表示valueAnimator的持续时间,这里设置为和最大倒计时时间相同
    private float duration;

    //10.最大倒计时时间,如果1分钟计时,这里就有600000ms,单位是ms
    private int maxTime=10000;
    //这里设置为10是为了避免  angleValue = currentValue/maxTime*360.0f除数为0的异常,如果既没有在xml中设置最大值就会报错,这是由绘制流程决定的。

    //11.当前的时间是指倒计时剩余时间,需要显示在圆中
    private int currentTime;

    private onFinishListener finishListenter;

    public TimerCircle(Context context) {
        this(context,null);//12.TimerCircle circle = new TimerCircle()时调用该构造方法
    }



    public TimerCircle(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);//13.通过xml使用自定义View时使用该构造方法
    }

    public TimerCircle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);//14.一般不会主动调用该方法,除非手动指定调用。

        //15.获取属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TimerCircle);
        circle_color = typedArray.getColor(R.styleable.TimerCircle_circleColor, Color.BLUE);
        ring_color = typedArray.getInteger(R.styleable.TimerCircle_ringColor, 0);
        ring_width = (int) typedArray.getDimension(R.styleable.TimerCircle_width, getResources().getDimension(R.dimen.width));
        text_color = typedArray.getColor(R.styleable.TimerCircle_path, Color.RED);
        text_size = (int) typedArray.getDimension(R.styleable.TimerCircle_textSize,getResources().getDimension(R.dimen.textSize));
        path_color = typedArray.getColor(R.styleable.TimerCircle_path, Color.RED);
        typedArray.recycle();
        InitPaint();

    }

    private void InitPaint() {
        mPaint = new Paint();
        mPaint.setColor(ring_color);
        mPaint.setAntiAlias(true);
        nPaint = new Paint();
        nPaint.setAntiAlias(true);
        nPaint.setColor(circle_color);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //16.如果padding和wrap-content失效,还要在这里处理
        int widthPixels = this.getResources().getDisplayMetrics().widthPixels;//17. 获取屏幕宽度
        int heightPixels = this.getResources().getDisplayMetrics().heightPixels;//18.获取屏幕高度

        //19.测量,目的是为了根据指定的宽高和屏幕的宽高最终确定圆的半径,四个中最小的即为半径
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int minWidth = Math.min(width, widthPixels);
        int minHeight = Math.min(height, heightPixels);

        setMeasuredDimension(Math.min(minHeight, minWidth), Math.min(minHeight, minWidth));
    }

    @Override
    protected void onDraw(Canvas canvas) {

        //20.确定内圆的半径
        int in_radius = (this.getWidth()-2*ring_width)/2;

        //21.确定圆的圆心
        int center = this.getWidth()-in_radius-ring_width;

        //22.绘制内圆和圆环
        drawInner(canvas, in_radius,center);

        //23.绘制倒计时的字体
        drawText(canvas,center);

    }



    private void drawInner(Canvas canvas, int radius, int center) {
        //24.先画出内圆
        canvas.drawCircle(center, center, radius, nPaint);

        //25.画圆环,设置为空心圆,指定半径为内圆的半径,画笔的宽度就是圆环的宽度
        mPaint.setStyle(Paint.Style.STROKE);//26.设置空心圆
        mPaint.setStrokeWidth(ring_width);//27.画笔宽度即圆环宽度
        mPaint.setColor(ring_color);//28. 圆环的颜色
        canvas.drawCircle(center, center, radius, mPaint);


         //30.内圆的外接矩形,有什么作用?绘制圆弧时根据外接矩形绘制
        RectF rectF = new RectF(center-radius,center-radius,center+radius,center+radius);

        //31.计算弧度,通过当前的currentValue的值得到
        angle_value = current_value * 360.0f / maxTime * 1.0f;

        //32.设置阴影大小和颜色
        mPaint.setShadowLayer(10, 10, 10, Color.BLUE);

        //33.指定线帽样式,可以理解为一条有宽度的直线的两端是带有弧度的
        mPaint.setStrokeCap(Paint.Cap.ROUND);

        //34.圆弧的颜色
        mPaint.setColor(path_color);

        //35.绘制圆弧
        canvas.drawArc(rectF, -90,angle_value , false, mPaint);
    }

    public void setDuration(int duration, final int maxTime) {
        //36.如果外部指定了最大倒计时的时间,则xml定义的最大倒计时无效,以外部设置的为准
        this.maxTime=maxTime;
        //37.持续时间和最大时间保持一致,方便计算
        this.duration=duration;
        if (animator != null) {
            animator.cancel();
        } else {
            animator = ValueAnimator.ofInt(0, maxTime);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //38.获取此时的进度
                    current_value = (int) animation.getAnimatedValue();
                    if(current_value==maxTime){
                        finishListenter.onFinish();
                    }
                    //39.invalidate()方法系统会自动调用 View的onDraw()方法。
                    invalidate();
                }
            });
            //40.线性插值器,匀速变化
            animator.setInterpolator(new LinearInterpolator());
        }
        animator.setDuration(duration);
        animator.start();
    }

    private void drawText(Canvas canvas,int center) {
        //41.计算当前的剩余的时间,单位s
        currentTime=(maxTime-current_value)/1000;

        //42.显示的倒计时字符串
        String Text=null;

        if(currentTime<10){
            Text="00:0"+currentTime;
        }else if(currentTime>=10&&currentTime<=60){
            Text="00:"+currentTime;
        }else if(currentTime>60&&currentTime<600){
            int min = currentTime/60;
            int sen = currentTime%60;
            if (sen<10) {
                Text="0"+min+":0"+sen;
            }else{
                Text="0"+min+":"+sen;
            }

        }else{
            int min = currentTime/60;
            int sen = currentTime%60;
            if (sen<10) {
                Text=min+":0"+sen;
            }else{
                Text=min+":"+sen;
            }
        }

        // 43.设置文字居中,以左下角为基准的(x,y)就是这里的center baseline。具体的关于drawText需要查看https://blog.csdn.net/harvic880925/article/details/50423762/
        mPaint.setTextAlign(Paint.Align.CENTER);
        // 44.设置文字颜色
        mPaint.setColor(text_color);
        mPaint.setTextSize(text_size);
        mPaint.setStrokeWidth(0);//清除画笔宽度
        mPaint.clearShadowLayer();//清除阴影
        // 45.文字边框
        Rect bounds = new Rect();
        // 46.获得绘制文字的边界矩形
        mPaint.getTextBounds(Text, 0, Text.length(), bounds);
        // 47.获取绘制Text时的四条线
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        // 48.计算文字的基线
        int baseline = center - (fontMetrics.bottom+fontMetrics.top)/2;
        // 49.绘制表示进度的文字
        canvas.drawText(Text, center, baseline, mPaint);

    }

    public interface onFinishListener{
       void onFinish();
    }

    public void setFinishListenter(onFinishListener listenter){
        this.finishListenter = listenter;
    }

}


使用

<com.example.view.TimerCircle
        android:id="@+id/timer"
        android:layout_marginLeft="60dp"
        android:layout_marginTop="66dp"
        android:layout_width="200dp"
        android:layout_height="200dp"
        app:width="20dp"
        app:circleColor="#CCCCCC"
        app:path="#FF6666"
        app:maxTime="10000"
        app:ringColor="#BBBBBB"
        app:textColor="#0099CC"
        app:textSize="28sp">

具体不多说了,注释非常清楚了,请看效果图
在这里插入图片描述

在这里插入图片描述

扩展——仪表盘

style.xml
<declare-styleable name="PanelView">
        <!--圆环颜色-->
        <attr name="ring_color" format="color"></attr>
        <!--内圆环颜色-->
        <attr name="back_color" format="color"></attr>
        <!--圆环宽度-->
        <attr name="ring_width" format="dimension"></attr>
        <!--圆环开始的角度,需要做异常检测-270-90-->
        <attr name="start_angle" format="integer"></attr>
        <!--圆环结束的角度-->
        <attr name="sweep_angle" format="integer"></attr>
        <!--字体颜色-->
        <attr name="text_color" format="color"></attr>
        <!--字体大小-->
        <attr name="text_size" format="dimension"></attr>
        <!--路径颜色-->
        <attr name="path_color" format="color"></attr>
    </declare-styleable>
代码和注释
package com.example.view;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

import com.example.binder.R;

import java.text.DecimalFormat;

//仪表盘自定义view
public class PanelView extends View {
    private Paint ringPaint;//圆环的画笔
    private Paint textPaint;//字体画笔
    private int width;//空间的宽度
    private int height;//空间的高度
    private int ring_color;//圆环颜色
    private int back_color;//背景颜色
    private int ring_width;//圆环宽度
    private int start_angle;//起始角度
    private int sweep_angle;//结束角度
    private int current_angle;//当前的角度
    private int text_color;//字体颜色
    private int text_size;//字体大小
    private int path_color;//路径颜色
    private int precent;//进度
    private ValueAnimator animator;

    public PanelView(Context context) throws Exception {
        this(context,null);
    }

    public PanelView(Context context, @Nullable AttributeSet attrs) throws Exception {
       this(context,attrs,0);
    }

    public PanelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) throws Exception {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.PanelView);
        ring_color = ta.getColor(R.styleable.PanelView_ring_color, Color.GREEN);
        back_color = ta.getColor(R.styleable.PanelView_back_color, Color.WHITE);
        ring_width = (int) ta.getDimension(R.styleable.PanelView_ring_width, getResources().getDimension(R.dimen.width));
        start_angle = ta.getInteger(R.styleable.PanelView_start_angle, -225);
        sweep_angle = ta.getInteger(R.styleable.PanelView_sweep_angle, 270);
        text_color = ta.getColor(R.styleable.PanelView_text_color, Color.RED);
        text_size = (int) ta.getDimension(R.styleable.PanelView_text_size, getResources().getDimension(R.dimen.textSize));
        path_color = ta.getColor(R.styleable.PanelView_path_color, Color.BLACK);
        ta.recycle();
        if(start_angle<-360||sweep_angle>360)
            throw new Exception("angel not allow");
        InitPaint();

    }

    private void InitPaint() {
        ringPaint = new Paint();
        textPaint = new Paint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width_pixels = getResources().getDisplayMetrics().widthPixels;
        int height_pixels = getResources().getDisplayMetrics().heightPixels;
        width = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getSize(heightMeasureSpec);
        //获取四个的最小值
        int layout_width = Math.min(width_pixels, width);
        int layout_height = Math.min(height_pixels, height);
        int final_width = Math.min(layout_height, layout_width);
        setMeasuredDimension(final_width,final_width);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //先话内部圆环
        int center = getWidth()/2;
        int radius = center - ring_width;
        drawRing(canvas,radius,center);
        drawText(canvas,radius,center);
    }


    private void drawRing(Canvas canvas, int radius, int center) {
         //画圆环
        RectF oval = new RectF(center-radius, center-radius, center+radius, center+radius);
        ringPaint.setStrokeWidth(0);
        ringPaint.setStyle(Paint.Style.FILL);
        ringPaint.setColor(back_color);
        ringPaint.setStrokeCap(Paint.Cap.ROUND);
        canvas.drawArc(oval, start_angle*1.0f, sweep_angle*1.0f, false,ringPaint );

        ringPaint.setStyle(Paint.Style.STROKE);
        ringPaint.setStrokeWidth(ring_width);
        ringPaint.setStrokeCap(Paint.Cap.ROUND);
        ringPaint.setColor(ring_color);
        canvas.drawArc(oval, start_angle*1.0f, sweep_angle*1.0f, false, ringPaint);

        ringPaint.setStyle(Paint.Style.STROKE);
        ringPaint.setStrokeWidth(ring_width);
        ringPaint.setStrokeCap(Paint.Cap.ROUND);
        ringPaint.setColor(path_color);
        canvas.drawArc(oval, start_angle*1.0f, current_angle*1.0f, false, ringPaint);
    }

    private void drawText(Canvas canvas, int radius, int center) {
         //字体
        String text = precent+"%";
        Rect rect = new Rect();
        textPaint.getTextBounds(text, 0, text.length(), rect);
        Paint.FontMetricsInt fontMetricsInt = textPaint.getFontMetricsInt();
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(text_color);
        textPaint.setTextSize(text_size);
        int baseline = center-(fontMetricsInt.bottom+fontMetricsInt.top)/2;
        canvas.drawText(text,center ,baseline,textPaint);


    }

    public void setDuration(final int duration){
        if(animator==null){
            animator = ValueAnimator.ofInt(0,duration);
        }
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
               int curValue = (int) animation.getAnimatedValue();
               precent = curValue*100/duration;
               current_angle=precent*sweep_angle/100;
               invalidate();
            }
        });
        animator.setDuration(duration);
        animator.start();
    }
}

效果图

在这里插入图片描述
在这里插入图片描述

扩展——线性进度条

自定义线性进度条

结束

有兴趣来这个群里交流呀
在这里插入图片描述

原文地址:https://www.cnblogs.com/hzcya1995/p/13309287.html