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();
    }
}

效果圖

在這里插入圖片描述
在這里插入圖片描述

擴展——線性進度條

自定義線性進度條

結束

有興趣來這個群里交流呀
在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM