• 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();
        }
    }
    
    
    效果图

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

    扩展——线性进度条

    自定义线性进度条

    结束

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

  • 相关阅读:
    字符串题表
    插头dp题表
    点分治题表
    Kd-tree题表
    【BZOJ 4605】崂山白花蛇草水 替罪羊树套线段树
    假的kd-tree小结
    【BZOJ 1492】 [NOI2007]货币兑换Cash 斜率优化DP
    CDQZ 2017 游记
    【BZOJ 1146】[CTSC2008]网络管理Network
    联赛之前的题表(已完成)汇总(可能有遗漏)
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13309287.html
Copyright © 2020-2023  润新知