项目地址: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>
我们还可以定义一个设置CircleNumberProgressBar的Style的一个属性
<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()方法保存画布状态,以免影响后续操作。在绘制圆形时,需要设置画笔的Style为STROKE表示空心,再调用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,哈哈