android:圆形进度条的实现

  • 这个需求,在我进上上家公司,做第一个项目的时候就遇到了。当时百度了一下,找到了夏安明童鞋的圆形进度条的实现。然后copy过去,很愉快地就解决了需求。很happy的样子。
  • 前几天,忽然想弄一下圆形进度条,于是,就弄了一个。记录在此,纪念一下。
  • 为了方便使用和扩展,我也弄了几个自定义属性
    • 可以自定义进度条颜色
    • 可以自定义进度条边框颜色
    • 可以自定义进度条宽度
    • 可以自定义有无边框
    • 可以自定义文字颜色
    • 可以自定义文字大小(这个貌似没有考虑边界情况)
    • 可以自定义文字是否显示
    • 可以自定义进度条是环形,还是扇形
  • 自定义属性文件如下attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="progress" format="reference|integer"/>
    <attr name="progressWidth" format="dimension|reference|float"/>
    <attr name="bordersWidth" format="dimension|reference|float"/>
    <attr name="borderVisible" format="boolean"/>
    <attr name="progressColor" format="color|reference|integer"/>
    <attr name="borderColor" format="color|reference|integer"/>
    <attr name="textColor" format="color|reference|integer"/>
    <attr name="textSize" format="dimension|reference|float"/>
    <attr name="textVisible" format="boolean"/>
    <attr name="useCenter" format="boolean"/>
    <declare-styleable name="CircleProgressBar">

        <attr name="progress"/>
        <attr name="progressWidth"/>
        <attr name="bordersWidth"/>
        <attr name="borderVisible"/>
        <attr name="progressColor"/>
        <attr name="borderColor"/>
        <attr name="textColor"/>
        <attr name="textSize"/>
        <attr name="textVisible"/>
        <attr name="useCenter"/>
    </declare-styleable>
</resources>
  • 然后代码如下:
/**
 * Created  on 2017/1/21.
 */

public class CircleProgressBar extends View {

    private final boolean useCenter;
    private int progress;

    private final int borderColor;
    private final float borderWidth;
    private final boolean textVisible;
    private float progressWidth;
    private int progressColor;
    private boolean borderVisible;
    private final int textColor;
    private float textSize;
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private int mRadius;
    private Rect bounds;
    private RectF oval;

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

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

    public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleProgressBar, 0, 0);
        // progress
        progress = array.getInt(R.styleable.CircleProgressBar_progress, 35);
        progressWidth = array.getDimension(R.styleable.CircleProgressBar_progressWidth, 20);
        Log.d("A", "progressWidth = " + progressWidth);
        progressColor = array.getColor(R.styleable.CircleProgressBar_progressColor, Color.BLUE);
        // border
        borderColor = array.getColor(R.styleable.CircleProgressBar_borderColor, Color.GRAY);
        borderWidth = array.getDimension(R.styleable.CircleProgressBar_bordersWidth, dp2Px(context, 0.3f));
        Log.d("A", "borderWidth = " + borderWidth);
        borderVisible = array.getBoolean(R.styleable.CircleProgressBar_borderVisible, true);
        // text
        // textSize --> todo
        textSize = array.getDimension(R.styleable.CircleProgressBar_textSize, dp2Px(context, 12));
        Log.d("A", "textSize = " + textSize);
        textColor = array.getColor(R.styleable.CircleProgressBar_textColor, Color.RED);
        textVisible = array.getBoolean(R.styleable.CircleProgressBar_textVisible, true);
        useCenter = array.getBoolean(R.styleable.CircleProgressBar_useCenter, false);
        array.recycle();
    }

    public static int dp2Px(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        init(w, h);
    }

    private void init(int w, int h) {
        this.mWidth = w;
        this.mHeight = w;
        this.mRadius = Math.min(w, h) / 2;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        bounds = new Rect();

        oval = new RectF();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = widthSize / 2;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = heightSize / 2;
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 1. drawRing
        drawRing(canvas);
        // 2. drawText
        drawText(canvas);
        // 3.drawProgress
        drawProgress(canvas);
    }

    private void drawProgress(Canvas canvas) {
        mPaint.setColor(progressColor);
        if (useCenter) {
            oval.set(0, 0, mRadius * 2, mRadius * 2);
            mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
            mPaint.setStrokeWidth(0);
        } else {
            float proRadius = mRadius - borderWidth - progressWidth / 2;
            oval.set(mRadius - proRadius, mRadius - proRadius, mRadius + proRadius, mRadius + proRadius);
            mPaint.setStrokeWidth(progressWidth);
            mPaint.setStyle(Paint.Style.STROKE);
        }
        mPaint.setAntiAlias(false);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        canvas.drawArc(oval, -90, progress * 1f / 100 * 360, useCenter, mPaint);
    }

    private void drawText(Canvas canvas) {
        if (textVisible) {
            mPaint.setStrokeWidth(0);
            mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
            mPaint.setColor(textColor);
            mPaint.setTextSize(textSize); // todo ->
            mPaint.setTextAlign(Paint.Align.CENTER); // 文字对齐方式为文字的中间(底部中点)
            String text;
            text = "" + progress + "%";
            mPaint.getTextBounds(text, 0, text.length(), bounds);
            Log.d("A", "" + bounds);
            // 让文字居中 ("要显示的文字",view横轴中点,view纵轴中点+文字高度/2,画笔)
            canvas.drawText(text, mRadius, mRadius + bounds.height() / 2, mPaint);
        }
    }

    private void drawRing(Canvas canvas) {
        if (borderVisible) {
            mPaint.setColor(borderColor);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(borderWidth);
            canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius, mPaint); // outer circle
            canvas.drawCircle(mWidth / 2, mHeight / 2,
                    mRadius - progressWidth - borderWidth, mPaint); // inner circle
        }
    }

    public synchronized void setProgress(@IntRange(from = 0, to = 100) int progress) {
        this.progress = progress;
        postInvalidate();
    }
}

代码不多,不到200行。使用更简单

<com.abc.circleprogressbar.CircleProgressBar
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/circle_progressBar"
        android:layout_width="80dp"
        android:layout_height="80dp"
        app:borderColor="@color/colorPrimary"
        app:borderVisible="true"
        app:bordersWidth="1dp"
        app:progress="0"
        app:progressColor="@color/cale_actionbar_tab_color"
        app:progressWidth="8dp"
        app:textColor="@color/colorPrimaryDark"
        app:textSize="14sp"
        app:textVisible="false"
        app:useCenter="true"
        />
  • 主要解释一下drawText(Canvas canvas)方法:
private void drawText(Canvas canvas) {
   if (textVisible) {
       mPaint.setStrokeWidth(0);
       mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
       mPaint.setColor(textColor);
       mPaint.setTextSize(textSize); // todo ->
       mPaint.setTextAlign(Paint.Align.CENTER); // 文字对齐方式为文字的中间(底部中点)
       String text;
       text = "" + progress + "%";
       mPaint.getTextBounds(text, 0, text.length(), bounds);
       Log.d("A", "" + bounds);
       // 让文字居中 ("要显示的文字",view横轴中点,view纵轴中点+文字高度/2,画笔)
       canvas.drawText(text, mRadius, mRadius + bounds.height() / 2, mPaint);
   }
}
  • 通过以上方式就可以让文字居中显示了。因为上述方式,首先将text的对齐坐标点,从左下角,移动到了底部中点位置。(是的,text的对齐坐标是左下角的点,而不是常见的左上角)。然后,将对齐坐标点放到view中间,让后向下移动半个text高度即可。这样子就让text完完全全的呈现在view的正中间了。

  • 彩蛋时刻:为了方便使用,已将代码开源到github,我的第一个开源库发布了

    • 使用非常简单

      allprojects {
      repositories {
        maven { url 'https://jitpack.io' }
      }
      }
      
      compile 'com.github.duckAndroid:circleProgressbar:0.0.2'
    • 布局文件中使用方式如下:

      <com.pythoncat.circleprogressbar.CircleProgressBar
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/circle_progressBar"
            android:layout_width="80dp"
            android:layout_height="80dp"
            app:borderColor="@color/colorPrimary"
            app:borderVisible="true"
            app:bordersWidth="1dp"
            app:progress="0"
            app:progressColor="@color/colorAccent"
            app:progressWidth="8dp"
            app:textColor="@color/colorPrimaryDark"
            app:textSize="14sp"
            app:textVisible="false"
            app:useCenter="true"/>
  • 彩蛋结束……………………

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值