CircleNumberProgressBar:显示数字的圆形进度条

项目地址:https://github.com/AlarmZeng/CircleNumberProgressBar

圆形的ProgressBar是经常使用的控件,能够显示当前的进度,但有时候可能还不够直观,原有控件显示进度时并不能准确的知道当前进度是多少,又或是遇到项目需求需要在进度条中间加上数字显示,嗯……项目需求,所以这个时候就需要自己对原有的ProgressBar进行改造了,自己动手,丰衣足食嘛!

在原有的ProgressBar的基础上进行改造,那么就新建一个类CircleNumberProgressBar,并让其继承ProgressBar,接着我们需要先定义好CircleNumberProgressBar需要的相关属性

  <declare-styleable name="CircleNumberProgressBar">
    
      <!-- 圆的半径 -->
      <attr name="cnpb_circle_radius" format="dimension"/>

      <!-- 进度条的宽度 -->
      <attr name="cnpb_bar_width" format="dimension" />
    
      <!-- 达到的进度颜色 -->
      <attr name="cnpb_reach_color" format="color" />
    
      <!-- 未达到的进度颜色 -->
      <attr name="cnpb_unreach_color" format="color" />
    
      <!-- 文字大小 -->
      <attr name="cnpb_text_size" format="dimension" />

      <!-- 文字颜色 -->
      <attr name="cnpb_text_color" format="color" />

      <!-- 文字是否显示 -->
      <attr name="cnpb_text_visibility" format="enum">
          <enum name="invisible" value="0"/>
          <enum name="visible" value="1"/>
      </attr>

      <!-- 单位 -->
      <attr name="cnpb_unit" format="string"/>

      <!-- 单位是否显示 -->
      <attr name="cnpb_unit_visibility" format="enum">
          <enum name="invisible" value="0"/>
          <enum name="visible" value="1"/>
      </attr>

  </declare-styleable>

我们还可以定义一个设置CircleNumberProgressBarStyle的一个属性

<attr name="styleCircleNumberProgressBar" format="reference" />

这样可以方便我们在styles文件中对CircleNumberProgressBar的属性进行统一设置,例如

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="styleCircleNumberProgressBar">@style/CircleNumberProgressBarTheme</item>
</style>

好了,定义并设置好属性之后,就需要在代码文件中对这些属性进行取值设置了,在构造函数中使用TypedArray对申明的属性进行取值,并在构造函数中对画笔Paint进行一些相关属性的设置

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

    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleNumberProgressBar, defStyleAttr, R.style.CircleNumberProgressBar_Style);

    mRadius = typedArray.getDimensionPixelSize(R.styleable.CircleNumberProgressBar_cnpb_circle_radius, dp2px(30));

    mBarWidth = typedArray.getDimensionPixelSize(R.styleable.CircleNumberProgressBar_cnpb_bar_width, dp2px(8));

    mReachColor = typedArray.getColor(R.styleable.CircleNumberProgressBar_cnpb_reach_color, 0xFF303F9F);

    mUnReachColor = typedArray.getColor(R.styleable.CircleNumberProgressBar_cnpb_unreach_color, 0xFFD3D6DA);

    mTextSize = typedArray.getDimensionPixelSize(R.styleable.CircleNumberProgressBar_cnpb_text_size, sp2px(14));

    mTextColor = typedArray.getColor(R.styleable.CircleNumberProgressBar_cnpb_text_color, 0xFF303F9F);

    mTextVisibility = typedArray.getInt(R.styleable.CircleNumberProgressBar_cnpb_text_visibility, VISIBLE);

    mUnit = typedArray.getString(R.styleable.CircleNumberProgressBar_cnpb_unit);

    mUnitVisibility = typedArray.getInt(R.styleable.CircleNumberProgressBar_cnpb_unit_visibility, VISIBLE);

    typedArray.recycle();

    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setAntiAlias(true);
    mPaint.setDither(true);
    mPaint.setStrokeCap(Paint.Cap.ROUND);

    rectF = new RectF(0, 0, mRadius * 2, mRadius * 2);

    mBound = new Rect();
}

设置属性之后,就到了自定义View的重点了,需要重写onMeasure()onDraw()方法,在onMeasure()方法中需要对View进行测量,从而确定View的测量宽高,在CircleNumberProgressBar中,onMeasure()里面的测量代码如下

@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    int width = 0;
    int height = 0;

    switch (widthSpecMode) {
        case MeasureSpec.AT_MOST :
            width = Math.min(mRadius * 2 + getPaddingLeft() + getPaddingRight() + mBarWidth, widthSpecSize);
            break;

        case MeasureSpec.EXACTLY :
            width = widthSpecSize;
            break;

        case MeasureSpec.UNSPECIFIED :
            width = mRadius * 2 + getPaddingRight() + getPaddingLeft() + mBarWidth;
            break;
    }

    switch (heightSpecMode) {
        case MeasureSpec.AT_MOST :
            height = Math.min(mRadius * 2 + getPaddingTop() + getPaddingBottom() + mBarWidth, heightSpecSize);
            break;

        case MeasureSpec.EXACTLY :
            height = heightSpecSize;
            break;

        case MeasureSpec.UNSPECIFIED :
            height = mRadius * 2 + getPaddingTop() + getPaddingBottom() + mBarWidth;
            break;
    }

    int result = Math.min(width, height);

    setMeasuredDimension(result, result);
}

在这里,会先分别获取宽高的测量模式SpecMode和对应测量模式下的规格大小SpecSize,接着会对测量模式SpecMode进行判定,并通过计算获取真正的宽高值,要注意的是,在计算时要把padding值算进去,否则会出现误差,在确定好宽高之后,再调用setMeasuredDimension方法设置宽高,这样就能确定View的大小了

测量了View的大小后,就要对View进行绘制了,我们需要绘制圆形,圆弧和文字,圆形是用来显示没有达到的进度,圆弧是显示达到的进度,文字则是在中间显示的进度值,看看代码

@Override
protected synchronized void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    String text = mUnitVisibility == VISIBLE ? getProgress() + mUnit : getProgress() + "";
    float baseline = getMeasuredHeight() / 2 + mPaint.getTextSize() / 2 - mPaint.getFontMetrics().descent - getPaddingTop();
    canvas.save();
    canvas.translate(getPaddingLeft() + mBarWidth / 2, getPaddingTop() + mBarWidth / 2);
    mPaint.setStyle(Paint.Style.STROKE);

    //先绘制未达到的进度条
    mPaint.setColor(mUnReachColor);
    mPaint.setStrokeWidth(mBarWidth);
    canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);

    //再绘制已经达到的进度条
    mPaint.setColor(mReachColor);
    mPaint.setStrokeWidth(mBarWidth);
    float angle = getProgress() * 1.0f / getMax() * 360;
    canvas.drawArc(rectF, 0, angle, false, mPaint);

    //绘制文字
    if (mTextVisibility == VISIBLE) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(mTextSize);
        mPaint.getTextBounds(text, 0, text.length(), mBound);
        canvas.drawText(text, mRadius - mBound.width() / 2, baseline, mPaint);
    }

    canvas.restore();

}

在绘制时,需要先调用canvas.save()方法保存画布状态,以免影响后续操作。在绘制圆形时,需要设置画笔的StyleSTROKE表示空心,再调用canvas.drawCircle方法绘制圆形,其圆心的x,y坐标和半径都是一样的

接着绘制进度条,这里是要绘制圆弧了,再绘制之前需要先计算圆弧的角度,这个比较简单,根据当前值 / 最大值 * 360的公式计算就可以了,在绘制圆弧时参数还需要一个RectF,这是用来指定绘制圆弧的外部巨星区域,这个只要设置其长高为半径的两倍就可以了rectF = new RectF(0, 0, mRadius * 2, mRadius * 2);

最后是绘制文字,绘制文字时要将画笔的Style设置为FILL,接着我们需要知道文字的宽度,可以通过调用mPaint.getTextBounds方法先获取到文字的边界,再确定宽度,注意的是在canvas.drawText的方法中,第三个参数y是表示文字的基线baseline在屏幕上的位置,所以要先计算基线baseline的高,这样才能把文字准确的绘制

绘制完成后就可进行使用啦,直接在xml文件上进行调用就可以了

<com.zht.circlenumberprogressbar.widget.CircleNumberProgressBar
    android:id="@+id/cnpb_progress"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    app:cnpb_circle_radius="50dp" />

属性值可以直接在xml文件设置,也可以在styles文件进行设置

<style name="CircleNumberProgressBarTheme" parent="CircleNumberProgressBar.Style">
    <item name="cnpb_text_color">#FF303F9F</item>
    <item name="cnpb_reach_color">#FF303F9F</item>
    <item name="cnpb_unreach_color">#FFD3D6DA</item>
    <item name="cnpb_text_size">16sp</item>
    <item name="cnpb_text_color">#FF303F9F</item>
    <item name="cnpb_text_visibility">visible</item>
    <item name="cnpb_unit">%</item>
    <item name="cnpb_unit_visibility">visible</item>
</style>

好了,到这里CielceNumberProgressBar也就基本完成了,具体的代码可以到我的github查看,项目地址:https://github.com/AlarmZeng/CircleNumberProgressBar,觉得不错的可以star或者follow,哈哈

    原文作者:HitoZeng
    原文地址: https://www.jianshu.com/p/1fa2e32ab6c3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞