Android自定义view动态绘制百分比圆环进度条

之前写了渐变进度条百分比的实现,今天我们来写下圆环百分比进度条。有时候我们需要自定义View才能满足我们的需求,首先,自定义View我们需要先继承View,添加类的构造方法,重写父类View的一些方法,例如onDraw,为了我们自定义的View在一个项目中能够重用,有时候我们需要自定义其属性,举个很简单的例子,我在项目中的多个界面使用我自定义的View,每个界面该自定义View的颜色都不相同,这时候如果没有自定义属性,那我们是不是需要构建不同颜色的View出来呢,这样子我们的代码就会显得很沉厄,所以这时候我们就需要自定义其属性来满足我们不同的需求,自定义属性呢,我们需要在values下新建立一个attrs.xml文件,在其中定义我们需要定义的属性,然后在自定义View中也要做相对应的修改,我们还是用一个小例子来看看自定义View和自定义属性的使用

咱们先看下效果



1、其实上面的圆环的颜色,圆环进度的颜色,是否显示进度的百分比,进度百分比的颜色,以及进度是实心还是空心等等这些都可以实现,只需要改下一下的代码就可以。我只显示上面的一种,相信各位都能实现出来。接下来我们就来教大家怎么来定义

attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PercentageRing">
        <attr name="circleColor" format="color"/>
        <attr name="radius" format="integer"/>
        <attr name="circleBackground" format="color"/>
        <attr name="ringColor" format="color"/>
        <attr name="textColor" format = "color"/>
    </declare-styleable>
</resources>
2、 自定义View的属性我们算是定义好了,相信大家也会获取该属性,我们直接使用context.obtainStyledAttributes(attrs, R.styleable.PercentageRing)来获取TypedArray,然后从TypedArray获取我们定义的属性,例如
// 自定义属性,attr // 使用TypedArray //自定义属性 values/attrs TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PercentageRing);
//中间圆的背景颜色 默认 - 白色 mCircleBackground = typedArray.getColor(R.styleable.PercentageRing_circleBackground, 0xff000000);
//外圆环的颜色 默认 - 灰色 roundColor = typedArray.getColor(R.styleable.PercentageRing_circleColor, 0xfff5f5f5);
//外圆环的颜色 动态画圆 - 默认-红色 mRingColor = typedArray.getColor(R.styleable.PercentageRing_ringColor, 0xfff5f5f5);
//中间圆的半径 默认为80 mRadius = typedArray.getInt(R.styleable.PercentageRing_radius, 80);
//字体颜色 默认 - 灰色 mTextColor = typedArray.getColor(R.styleable.PercentageRing_textColor, 0xff999999);
//最后一定要调用这个 释放掉TypedArray typedArray.recycle();
属性颜色,大家可以根据需求是定义,里面的注释也写得很明白。

3、下面是自定义View的全部代码

public class CircleProgressBar extends View {
    /**  * 绘制百分比的圆,一共有四部分,分别是里面的文字、背景圆、默认圆环、开始绘制的圆环;  * 思路:首先需要四支画笔, 设置画笔对应的属性等;  */  private Paint roundColorPaint;//外圆画笔  private Paint mCirclePaint;//中心园的画笔  private Paint mTextPaint;//文字的画笔  private Paint mArcPaint;//外圆环的画笔  private int mCircleX;//设置圆心x坐标  private int mCircleY;//设置圆心y坐标  private float mCurrentAngle;//当前角度  private RectF mArcRectF;//画中心园的外接矩形,用来画圆环用  private float mStartSweepValue;//圆环开始角度  private float mTargetPercent;//设置目标的百分比(也就是后台返回的数据)  private float mCurrentPercent;//当前百分比   private int mRadius;//圆的半径  private int mCircleBackground;//中间圆的背景颜色  private int mRingColor;//外圆环的颜色 第二次画圆的颜色  private int roundColor; //外圆环的颜色 第一次画圆的颜色  private int mTextSize;//字体大小  private int mTextColor;//字体颜色  public CircleProgressBar(Context context) {
        super(context);
        //初始化数据  init(context);
    }

    public CircleProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 自定义属性,attr  // 使用TypedArray  //自定义属性 values/attr  TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PercentageRing);
        //中间圆的背景颜色 默认 - 白色  mCircleBackground = typedArray.getColor(R.styleable.PercentageRing_circleBackground, 0xff000000);
        //外圆环的颜色 默认 - 灰色  roundColor = typedArray.getColor(R.styleable.PercentageRing_circleColor, 0xfff5f5f5);
        //外圆环的颜色 动态画圆 - 默认-红色  mRingColor = typedArray.getColor(R.styleable.PercentageRing_ringColor, 0xfff5f5f5);
        //中间圆的半径 默认为80  mRadius = typedArray.getInt(R.styleable.PercentageRing_radius, 80);
        //字体颜色 默认 - 灰色  mTextColor = typedArray.getColor(R.styleable.PercentageRing_textColor, 0xff999999);
        //最后一定要调用这个 释放掉TypedArray  typedArray.recycle();
        //初始化数据  init(context);
    }

    public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }
    private void init(Context context){
        //圆环开始角度 -90° 正北方向  mStartSweepValue = -90;
        //当前角度  mCurrentAngle = 0;
        //当前百分比  mCurrentPercent = 0;
        //设置中心园的画笔  mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setColor(mCircleBackground);
        mCirclePaint.setStyle(Paint.Style.FILL);
        //设置文字的画笔  mTextPaint = new Paint();
        mTextPaint.setColor(mTextColor);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setStrokeWidth((float) (0.025*mRadius));
        mTextPaint.setTextSize(mRadius/2);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        //设置外圆环的画笔  mArcPaint = new Paint();
        roundColorPaint = new Paint();
        roundColorPaint.setColor(roundColor);
        mArcPaint.setAntiAlias(true);
        mArcPaint.setColor(mRingColor);
        mArcPaint.setStyle(Paint.Style.STROKE);
        mArcPaint.setStrokeWidth((float) (0.075*mRadius));

        //获得文字的字号 因为要设置文字在圆的中心位置  mTextSize = (int) mTextPaint.getTextSize();

    }
    //主要是测量wrap_content时候的宽和高,因为宽高一样,只需要测量一次宽即可,高等于宽  @Override  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measure(widthMeasureSpec), measure(widthMeasureSpec));
        //设置圆心坐标  mCircleX = getMeasuredWidth()/2;
        mCircleY = getMeasuredHeight()/2;
        //如果半径大于圆心横坐标,需要手动缩小半径的值,否则就画到外面去了  if (mRadius>mCircleX) {
            //设置半径大小为圆心横坐标到原点的距离  mRadius = mCircleX;
            mRadius = (int) (mCircleX-0.075*mRadius);
            //因为半径改变了,所以要重新设置一下字体宽度  mTextPaint.setStrokeWidth((float) (0.025*mRadius));
            //重新设置字号  mTextPaint.setTextSize(mRadius/2);
            //重新设置外圆环宽度  mArcPaint.setStrokeWidth((float) (0.075*mRadius));
            //重新获得字号大小  mTextSize = (int) mTextPaint.getTextSize();
        }
        //画中心园的外接矩形,用来画圆环用  mArcRectF = new RectF(mCircleX-mRadius, mCircleY-mRadius, mCircleX+mRadius, mCircleY+mRadius);
    }

    //当wrap_content的时候,view的大小根据半径大小改变,但最大不会超过屏幕  private int measure(int measureSpec){
        int result=0;
        //1、先获取测量模式 和 测量大小  //2、如果测量模式是MatchParent 或者精确值,则宽为测量的宽  //3、如果测量模式是WrapContent ,则宽为 直径值 与 测量宽中的较小值;否则当直径大于测量宽时,会绘制到屏幕之外;  int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }else {
            result =(int) (1.075*mRadius*2);
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;

    }

    //开始画中间圆、文字和外圆环  @Override  protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画中间圆  canvas.drawCircle(mCircleX, mCircleY, mRadius, mCirclePaint);
        //画圆环  roundColorPaint.setStyle(Paint.Style.STROKE);
        //设置外圆环的宽度(默认灰色的圆环)  roundColorPaint.setStrokeWidth((float) (0.075*mRadius));
        canvas.drawCircle(mCircleX, mCircleY, mRadius, roundColorPaint);
        //动态绘制红色的圆环  canvas.drawArc(mArcRectF, mStartSweepValue ,mCurrentAngle, false, mArcPaint);
        //画文字 - 此处可以注销(原因:不能绘制有小数的百分比,例如:1.4% 如果是5%、12%这些可以不用注销下面代码),自定义布局将获取的值写在Textview上,  // 再使用下面的setTargetPercent方法获取数据动态画圆  canvas.drawText(String.valueOf(mCurrentPercent)+"%", mCircleX, mCircleY+mTextSize/4, mTextPaint);
        //判断当前百分比是否小于设置目标的百分比  if (mCurrentPercent<mTargetPercent) {
            //当前百分比+1  mCurrentPercent+=1;
            //当前角度+360  mCurrentAngle+=3.6;
            //每10ms重画一次  postInvalidateDelayed(10);
        }
    }
    //设置目标的百分比  public void setTargetPercent(float percent){
        this.mTargetPercent = percent;
    }
}

此时对应的布局为:

<com.ky.circleprogressbar.CircleProgressBar  android:id="@+id/recommend_progressBar"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  app:circleBackground="#00000000"  android:layout_centerHorizontal="true"  app:ringColor="#ffea4745"  app:textColor="#ff999999"/>
该布局的缺点是  圆环里面画出的百分比数字只能为整数(34%、77%),我试过没改过来。大家可以试下。


下面是我自定义 一个显示百分比数字的布局来代替画笔画的百分比:

<LinearLayout  android:layout_width="match_parent"  android:layout_height="match_parent"  android:padding="40dp"  android:orientation="horizontal">
    <RelativeLayout  android:layout_width="wrap_content"  android:layout_height="wrap_content">

        <com.ky.circleprogressbar.CircleProgressBar  android:id="@+id/recommend_progressBar"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  app:circleBackground="#00000000"  android:layout_centerHorizontal="true"  app:ringColor="#ffea4745"  app:textColor="#ff999999"/>

        <LinearLayout  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:layout_centerVertical="true"  android:layout_centerHorizontal="true"  android:gravity="center"  android:orientation="horizontal"  android:visibility="visible">

            <TextView  android:id="@+id/recommend_rate_circle"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:text=""  android:textColor="#ff999999"  android:textSize="14.0sp" />

            <TextView  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:text="%"  android:textColor="#ff999999"  android:textSize="12.0sp" />
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>
使用自定义显示数字百分比需要注释掉下面的代码:
 
 
canvas.drawText(String.valueOf(mCurrentPercent)+"%", mCircleX, mCircleY+mTextSize/4, mTextPaint);
这样就显示这样(34.5%、55.8%)的百分比了。


本库的地址:https://github.com/kongyuaner/CircleProgressBar