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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容