«

打造一个可分割的华丽圆形进度条,适合倒计时等场景

时间:2024-3-2 18:28     作者:韩俊     分类: Android


一、需要的知识点介绍

又到了周末时光,前段时间一直在忙自己开发的app,最近新版上架了,终于有时间干些其他事情了,这个进度条是从项目中提取出来的,主要知识点就是自定义控件,自定义控件的三部曲:测量、布局、绘制,在这里主要是使用绘制。一直想写好一个自己的博客,作为一个知识分享的空间,也同时是自己知识的记事本,但总是被各种时间占用,不知道你们是不是和我一样。


这里主要是对api的认识,重点是使用以下函数进行画弧,下面是IDE直接提示的函数说明:

void android.graphics.Canvas.drawArc(RectF oval,
float startAngle, float sweepAngle, boolean useCenter, Paint paint)

Draw the specified arc, which will be scaled to fit inside the specified oval.

If the start angle is negative or >= 360, the start angle is treated as start angle modulo 360.

If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is negative, the sweep angle is treated as sweep angle modulo 360

The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0 degrees (3 o'clock on a watch.)

Parameters:oval The bounds of oval used to define the shape and size of the arcstartAngle Starting angle (in degrees) where the arc beginssweepAngle Sweep angle (in degrees) measured clockwiseuseCenter If true, include the center of the oval in the arc, and close it if it is being stroked. This will draw a wedgepaint The paint used to draw the arc

我们主要看一下这个函数的几个参数的意义,第一个参数oval需要传入一个RectF,也就是包裹圆形(椭圆)的最大矩形;startAngle起始角度,从0开始计算;sweepAngle顺时针扫描角度,这个参数是个关键,指的是从起始角度开始需要画的角度,而不是从0开始的结束角度;useCenter,true画实心的圆、false只画边框;paint需要的Paint对象,可设置一系列属性,如抗锯齿、画笔颜色等等

二、自定义可分割圆形进度条

先来看下效果图:


attrs.xml文件,这里面定义了RoundProgressBar可在layout中使用的特定属性

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="RoundProgressBar">
        <!-- 圆环背景颜色 -->
        <attr name="roundColor" format="color" />
        <!-- 圆环进度颜色 -->
        <attr name="roundProgressColor" format="color" />
        <!-- 圆环宽度 -->
        <attr name="roundWidth" format="dimension" />
        <!-- 圆环被分割的份数 -->
        <attr name="roundParts" format="integer" />
        <!-- 圆环中间的字体颜色 -->
        <attr name="roundProgressTextColor" format="color" />
        <!-- 圆环中间的字体大小 -->
        <attr name="roundProgressTextSize" format="dimension" />
        <!-- 进度条最大值 -->
        <attr name="max" format="integer" />
        <!-- 圆环中间的字体是否显示 -->
        <attr name="textIsDisplayable" format="boolean" />
        <!-- 圆环是否为实心 -->
        <attr name="style">
            <!-- 空心 -->
            <enum name="STROKE" value="0" />
            <!-- 实心 -->
            <enum name="FILL" value="1" />
        </attr>
    </declare-styleable>

</resources>
RoundProgressBar.java,主要的实现类,通过继承View并重写onDraw方法实现圆形可分割进度条

package com.example.roundpartsprogressbar;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;

public class RoundProgressBar extends View {
    /**
     * 画笔对象的引用
     */
    private Paint paint;

    /**
     * 圆环的颜色
     */
    private int roundColor;

    /**
     * 圆环进度的颜色
     */
    private int roundProgressColor;

    /**
     * 中间进度百分比的字符串的颜色
     */
    private int textColor;

    /**
     * 中间进度百分比的字符串的字体
     */
    private float textSize;

    /**
     * 圆环的宽度
     */
    private float roundWidth;

    /**
     * 圆环被分割的份数
     */
    private float roundParts;

    /**
     * 最大进度
     */
    private int max;

    /**
     * 当前进度
     */
    private int progress;
    /**
     * 是否显示中间的进度
     */
    private boolean textIsDisplayable;

    /**
     * 进度的风格,实心或者空心
     */
    private int style;

    public static final int STROKE = 0;
    public static final int FILL = 1;

    public RoundProgressBar(Context context) {
        this(context, null);
    }

    public RoundProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        paint = new Paint();

        TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
                R.styleable.RoundProgressBar);

        // 获取自定义属性和默认值
        roundColor = mTypedArray.getColor(
                R.styleable.RoundProgressBar_roundColor, Color.RED);
        roundProgressColor = mTypedArray.getColor(
                R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN);
        textColor = mTypedArray.getColor(
                R.styleable.RoundProgressBar_roundProgressTextColor,
                Color.GREEN);
        textSize = mTypedArray.getDimension(
                R.styleable.RoundProgressBar_roundProgressTextSize, 15);
        roundWidth = mTypedArray.getDimension(
                R.styleable.RoundProgressBar_roundWidth, 5);
        roundParts = mTypedArray.getInteger(
                R.styleable.RoundProgressBar_roundParts, 50);
        max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100);
        textIsDisplayable = mTypedArray.getBoolean(
                R.styleable.RoundProgressBar_textIsDisplayable, true);
        style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);

        mTypedArray.recycle();
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        /**
         * 画最外层的大圆环
         */
        int centre = getWidth() / 2; // 获取圆心的x坐标
        int radius = (int) (centre - roundWidth / 2); // 圆环的半径
        paint.setColor(roundColor); // 设置圆环的颜色
        paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
        paint.setAntiAlias(true); // 消除锯齿
        RectF oval = new RectF(centre - radius, centre - radius, centre
                + radius, centre + radius); // 用于定义的圆弧的形状和大小的界限
        float parts = 360.0f / (roundParts * 2);
        switch (style) {
        case STROKE: {
            paint.setStyle(Paint.Style.STROKE); // 设置空心
            for (int i = 0; i < roundParts * 2; i++) {
                if (0 == i % 2) {
                    canvas.drawArc(oval, parts * i, parts, false, paint); // 根据进度画圆弧
                }
            }
            break;
        }
        case FILL: {
            paint.setStyle(Paint.Style.FILL); // 设置实心
            for (int i = 0; i < roundParts * 2; i++) {
                if (0 == i % 2) {
                    canvas.drawArc(oval, parts * i, parts, true, paint); // 根据进度画圆弧
                }
            }
            break;
        }
        }

        /**
         * 画进度百分比
         */
        paint.setStrokeWidth(0);
        paint.setColor(textColor);
        paint.setTextSize(textSize);
        paint.setTypeface(Typeface.DEFAULT_BOLD); // 设置字体
        float textWidth = paint.measureText("" + progress); // 测量字体宽度,我们需要根据字体的宽度设置在圆环中间
        if (textIsDisplayable && progress != 0 && style == STROKE) {
            canvas.drawText(progress + "", centre - textWidth / 2, centre
                    + textSize / 2, paint); // 画出进度百分比
        }

        /**
         * 画圆弧 ,画圆环的进度
         */

        // 设置进度是实心还是空心
        paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
        paint.setColor(roundProgressColor); // 设置进度的颜色
        float arcLength = 360.0f * progress / max;
        switch (style) {
        case STROKE: {
            paint.setStyle(Paint.Style.STROKE);
            for (int i = 0; i < arcLength / parts; i++) {
                if (0 == i % 2) {
                    if (i == (int) (arcLength / parts)) {
                        canvas.drawArc(oval, i * parts, arcLength - parts * i,
                                false, paint);
                    } else {
                        canvas.drawArc(oval, parts * i, parts, false, paint);
                    }
                }
            }

            break;
        }
        case FILL: {
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
            if (progress != 0)
                for (int i = 0; i < arcLength / parts; i++) {
                    if (0 == i % 2) {
                        if (i == (int) (arcLength / parts)) {
                            canvas.drawArc(oval, i * parts, arcLength - parts
                                    * i, true, paint);
                        } else {
                            canvas.drawArc(oval, parts * i, parts, true, paint);
                        }
                    }
                }
            break;
        }
        }

    }

    public synchronized int getMax() {
        return max;
    }

    /**
     * 设置进度的最大值
     * 
     * @param max
     */
    public synchronized void setMax(int max) {
        if (max < 0) {
            throw new IllegalArgumentException("max not less than 0");
        }
        this.max = max;
    }

    /**
     * 获取进度.需要同步
     * 
     * @return
     */
    public synchronized int getProgress() {
        return progress;
    }

    /**
     * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步 刷新界面调用postInvalidate()能在非UI线程刷新
     * 
     * @param progress
     */
    public synchronized void setProgress(int progress) {
        if (progress < 0) {
            throw new IllegalArgumentException("progress not less than 0");
        }
        if (progress > max) {
            progress = max;
        }
        if (progress <= max) {
            this.progress = progress;
            postInvalidate();
        }

    }

    public int getCricleColor() {
        return roundColor;
    }

    public void setCricleColor(int cricleColor) {
        this.roundColor = cricleColor;
    }

    public int getCricleProgressColor() {
        return roundProgressColor;
    }

    public void setCricleProgressColor(int cricleProgressColor) {
        this.roundProgressColor = cricleProgressColor;
    }

    public int getTextColor() {
        return textColor;
    }

    public void setTextColor(int textColor) {
        this.textColor = textColor;
    }

    public float getTextSize() {
        return textSize;
    }

    public void setTextSize(float textSize) {
        this.textSize = textSize;
    }

    public float getRoundWidth() {
        return roundWidth;
    }

    public void setRoundWidth(float roundWidth) {
        this.roundWidth = roundWidth;
    }
}

三、使用自定义可分割圆形进度条
和使用其他自定义控件没两样,首先需要xmlns:android_custom="http://schemas.android.com/apk/res/com.example.roundpartsprogressbar&quot;,然后开始使用自定义控件

以下是完整的activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:android_custom="http://schemas.android.com/apk/res/com.example.roundpartsprogressbar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:background="@android:color/white" >

    <com.example.roundpartsprogressbar.RoundProgressBar
        android:id="@+id/rpb"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android_custom:roundColor="#F26C4F"
        android_custom:roundParts="60"
        android_custom:roundProgressColor="#FF961F21"
        android_custom:roundProgressTextColor="#FF961F21"
        android_custom:roundProgressTextSize="40sp"
        android_custom:roundWidth="15dp"
        android_custom:textIsDisplayable="true" 
        android_custom:style="STROKE" />

</RelativeLayout>
MainActivity.java,这里实现了一个倒计时的界面,默认60秒倒计时。

package com.example.roundpartsprogressbar;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
    private RoundProgressBar rpb;
    private int time = 60;
    private TimerTask timerTask;
    private Timer timer = new Timer();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rpb = (RoundProgressBar) findViewById(R.id.rpb);
        rpb.setMax(time);
        rpb.setProgress(time);
        timerTask = new MyTimerTask();
        timer.schedule(timerTask, 1000, 1000);

    }

    private class MyTimerTask extends TimerTask {
        @Override
        public void run() {
            rpb.setProgress(time--);
            if (time == -1) {
                //倒计时为0的时候做自己需要处理的业务逻辑
                cancelRestTimerTask();
            }
        }
    }

    /**
     * 取消RestTimerTask
     */
    private void cancelRestTimerTask() {
        if (timerTask != null) {
            timerTask.cancel();
        }
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        cancelRestTimerTask();
    }
}

四、demo下载
点此下载

标签: android

热门推荐