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

来源:互联网 发布:php个人博客网站代码 编辑:程序博客网 时间:2024/06/05 03:45

项目地址: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()里面的测量代码如下

@Overrideprotected 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进行绘制了,我们需要绘制圆形,圆弧和文字,圆形是用来显示没有达到的进度,圆弧是显示达到的进度,文字则是在中间显示的进度值,看看代码

@Overrideprotected 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,哈哈