自定义NumProgressBar(圆形,线形)
之前在GitHub上看到一个开源的控件NumProgressBar,它与系统的控件ProgressBar没有任何继承关系,是通过继承View自定义的,代码写的也是相对比较多,只有线性的进度。
在这里我们可以通过继承系统的ProgressBar来达到相同的效果且支持圆形进度。
效果图:
NumProgressBar分析
自定义的流程无非就是测量,绘制的过程,无可避免需要重写onMeasure,onDraw方法,同时也需要定义相关的属性
1:圆形进度条
通过观察可以发现,圆形进度条有七个属性需要定义:圆形的半径,圆环的颜色,圆环的宽度,进度的颜色,进度的宽度,字体的颜色,字体的宽度。实现也是比较简单,就是在onMeasure方法中进行测量,来确定半径的大小,在onDraw方法中绘制圆,文字及圆弧进度,是通过继承系统的ProgressBar实现的,所以关于进度的获取可以通过getProgress()直接获取。
2:线形进度条
线性进度条和圆形进度条属性定义差不多,也是七个属性,只不过不再需要半径属性,但它需要一个偏移量的属性,来确定字体左右之间的距离。
圆形进度条实现
需要在values文件夹下新建attrs.xml文件,添加需要的 属性
<declare-styleable name="CircleNumberProgressBar">
<attr name="circleprogress_reache_color" format="color"/>
<attr name="circleprogress_reache_height" format="dimension"/>
<attr name="circleprogress_unreache_color" format="color"/>
<attr name="circleprogress_unreache__height" format="dimension"/>
<attr name="circleprogress_text_color" format="color"/>
<attr name="circleprogress_text_size" format="dimension"/>
<attr name="circleprogress_radius" format="dimension"/>
</declare-styleable>
新建CircleNumberProgressBar类,继承系统的ProgressBar,添加默认属性,其中字体大小使用sp,间距使用dp,需要把dp,sp转化为px;
private static final int DEFAULT_TEXT_COLOR = 0XFFFF4081;
private static final int DEFAULT_TEXT_SIZE = 14;
private static final int DEFAULT_UNREACH_COLOR = 0XFF3F51B5;
private static final int DEFAULT_UNREACH_HEIGHT = 2;
private static final int DEFAULT_REACH_COLOR = DEFAULT_TEXT_COLOR;
private static final int DEFAULT_REACH_HEIGHT = 3;
private static final int DEFAULT_RADIUS = 32;
protected int mTextColor = DEFAULT_TEXT_COLOR;
protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);
protected int mUnreachColor = DEFAULT_UNREACH_COLOR;
protected int mUnreachHeight = dp2px(DEFAULT_UNREACH_HEIGHT);
protected int mReachColor = DEFAULT_REACH_COLOR;
protected int mReachHeight = dp2px(DEFAULT_REACH_HEIGHT);
protected int mRadius = dp2px(DEFAULT_RADIUS);
protected Paint mPaint;
protected int mMaxPaintWidth;
dp,sp与px之间转化方法
public int sp2px(int spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics());
}
public int dp2px(int dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
}
在构造方法中添加init()方法,用于初始化
public CircleNumberProgressBar(Context context) {
this(context,null);
}
public CircleNumberProgressBar(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CircleNumberProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
init()初始化方法
public void init(AttributeSet attrs){
final TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CircleNumberProgressBar);
mTextColor = ta.getColor(R.styleable.CircleNumberProgressBar_circleprogress_text_color, mTextColor);
mTextSize = (int) ta.getDimension(R.styleable.CircleNumberProgressBar_circleprogress_text_size, mTextSize);
mReachColor = ta.getColor(R.styleable.CircleNumberProgressBar_circleprogress_reache_color, mReachColor);
mReachHeight = (int) ta.getDimension(R.styleable.CircleNumberProgressBar_circleprogress_reache_height, mReachHeight);
mUnreachColor = ta.getColor(R.styleable.CircleNumberProgressBar_circleprogress_unreache_color, mUnreachColor);
mUnreachHeight = (int) ta.getDimension(R.styleable.CircleNumberProgressBar_circleprogress_unreache__height, mUnreachHeight);
mRadius = (int) ta.getDimension(R.styleable.CircleNumberProgressBar_circleprogress_radius, mRadius);
ta.recycle();
mPaint = new Paint();
mPaint.setTextSize(mTextSize);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
测量宽度与高度
mMaxPaintWidth变量用来表示边缘绘制的一个宽度,该宽度取决于mUnreachHeight和mReachHeight的最大值
expect变量表示该控件请求的一个大小值,然后通过resolveSize方法交个系统进行计算和分配。
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMaxPaintWidth=Math.max(mReachHeight,mUnreachHeight);
int expect=getPaddingRight() + getPaddingLeft() + mRadius * 2 + mMaxPaintWidth;
int weight = resolveSize(expect,widthMeasureSpec);
int height = resolveSize(expect,heightMeasureSpec);
int readWidth=Math.min(weight,height);
mRadius=(readWidth-getPaddingLeft()-getPaddingRight()-mMaxPaintWidth)/2;
setMeasuredDimension(readWidth, readWidth);
}
绘制
@Override
protected synchronized void onDraw(Canvas canvas) {
String text=getProgress()+"%";
float textWeight=mPaint.measureText(text);
float textHeight=(mPaint.descent()+mPaint.ascent())/2;
canvas.save();
canvas.translate(getPaddingLeft()+mMaxPaintWidth/2,getPaddingRight()+mMaxPaintWidth/2);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mUnreachColor);
mPaint.setStrokeWidth(mUnreachHeight);
canvas.drawCircle(mRadius,mRadius,mRadius,mPaint);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mTextColor);
canvas.drawText(text,mRadius-textWeight/2,mRadius-textHeight,mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mReachHeight);
float sweepAngle=getProgress()*1.0f/getMax()*360;
canvas.drawArc(new RectF(0,0,mRadius*2,mRadius*2),-90,sweepAngle,false,mPaint);
canvas.restore();
}
线形进度条实现
自定义属性
<declare-styleable name="NumberProgressBar">
<attr name="numprogress_reache_color" format="color"/>
<attr name="numprogress_reache_height" format="dimension"/>
<attr name="numprogress_unreache_color" format="color"/>
<attr name="numprogress_unreache__height" format="dimension"/>
<attr name="numprogress_text_color" format="color"/>
<attr name="numprogress_text_size" format="dimension"/>
<attr name="numprogress_offset" format="dimension"/>
</declare-styleable>
默认属性
private static final int DEFAULT_TEXT_COLOR = 0XFFFF4081;
private static final int DEFAULT_TEXT_SIZE = 12;
private static final int DEFAULT_UNREACH_COLOR = 0XFF3F51B5;
private static final int DEFAULT_UNREACH_HEIGHT = 2;
private static final int DEFAULT_REACH_COLOR = DEFAULT_TEXT_COLOR;
private static final int DEFAULT_REACH_HEIGHT = 2;
private static final int DEFAULT_OFFSET = 10;
protected int mTextColor = DEFAULT_TEXT_COLOR;
protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);
protected int mUnreachColor = DEFAULT_UNREACH_COLOR;
protected int mUnreachHeight = dp2px(DEFAULT_UNREACH_HEIGHT);
protected int mReachColor = DEFAULT_REACH_COLOR;
protected int mReachHeight = dp2px(DEFAULT_REACH_HEIGHT);
protected int mOffset = dp2px(DEFAULT_OFFSET);
protected Paint mPaint ;
protected Paint mTextPaint;
protected int mRealWidth;
构造方法
public NumberProgressBar(Context context) {
this(context,null);
}
public NumberProgressBar(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public NumberProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
初始化方法init()
public void init(AttributeSet attrs){
final TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.NumberProgressBar);
mTextColor = ta.getColor(R.styleable.NumberProgressBar_numprogress_text_color, mTextColor);
mTextSize = (int) ta.getDimension(R.styleable.NumberProgressBar_numprogress_text_size, mTextSize);
mReachColor = ta.getColor(R.styleable.NumberProgressBar_numprogress_reache_color, mReachColor);
mReachHeight = (int) ta.getDimension(R.styleable.NumberProgressBar_numprogress_reache_height, mReachHeight);
mUnreachColor = ta.getColor(R.styleable.NumberProgressBar_numprogress_unreache_color, mUnreachColor);
mUnreachHeight = (int) ta.getDimension(R.styleable.NumberProgressBar_numprogress_unreache__height, mUnreachHeight);
mOffset = (int) ta.getDimension(R.styleable.NumberProgressBar_numprogress_offset, mOffset);
ta.recycle();
mPaint = new Paint();
mTextPaint=new Paint();
mTextPaint.setTextSize(mTextSize);
mTextPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
测量方法
在改方法中我们只需要对高度进行测量计算就可以
它的高度由文字,mReachHeight,mUnreachHeight三者的高度来确定,我们需要获取三者的最大值作为控件的高度,至于宽度,使用系统分配的就可以
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int textHeight = (int) (mTextPaint.descent() - mTextPaint.ascent());
int maxHeight=Math.max(textHeight,Math.max(mReachHeight,mUnreachHeight));
int expectHeight=getPaddingTop()+getPaddingBottom()+maxHeight;
int heightVal=resolveSize(expectHeight,heightMeasureSpec);
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(widthVal,heightVal);
mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
绘制
在绘制过程中需要一个布尔的变量noNeedUnreach 来标识是否绘制完成
该绘制分为三个部分,未绘制,已绘制和进度文字,当绘制的宽度超过控件宽度,说明绘制已经完成
@Override
protected synchronized void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(getPaddingLeft(), getHeight() / 2);
boolean noNeedUnreach = false;
float radio = getProgress() * 1.0f / getMax();
String text = getProgress() + "%";
int textWidth = (int) mTextPaint.measureText(text);
float progressX = radio * mRealWidth;
if (progressX + textWidth > mRealWidth) {
progressX = mRealWidth - textWidth;
noNeedUnreach = true;
}
float endx = progressX - mOffset / 2;
if (endx > 0) {
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mReachHeight);
canvas.drawLine(0, 0, endx, 0, mPaint);
}
mTextPaint.setColor(mTextColor);
mTextPaint.setStrokeWidth(mUnreachHeight);
int y = (int) (-(mTextPaint.descent() + mTextPaint.ascent()) / 2);
canvas.drawText(text, progressX, y, mTextPaint);
if (!noNeedUnreach) {
float startX = progressX + mOffset / 2 + textWidth;
mPaint.setColor(mUnreachColor);
mPaint.setStrokeWidth(mUnreachHeight);
canvas.drawLine(startX, 0, mRealWidth, 0, mPaint);
}
canvas.restore();
}
测试代码
布局文件
如果使用自定属性,需要添加
xmlns:app="http://schemas.android.com/apk/res-auto"
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity"
>
<com.mersens.view.CircleNumberProgressBar
android:id="@+id/progressbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:layout_margin="10dp"
app:circleprogress_radius="32dp"
app:circleprogress_text_size="16sp"
/>
<com.mersens.view.NumberProgressBar
android:id="@+id/numberProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:numprogress_reache_color="#03a9f4"
app:numprogress_text_color="#ff5722"
app:numprogress_unreache_color="#673ab7"
/>
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
private CircleNumberProgressBar progressbar;
private NumberProgressBar numberProgressBar;
private MyRunnable myRunnable=new MyRunnable();
private static final int DELAYED_TIME=100;
private int progress=0;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
public void init() {
progressbar = (CircleNumberProgressBar) findViewById(R.id.progressbar);
numberProgressBar=(NumberProgressBar)findViewById(R.id.numberProgressBar);
handler.postDelayed(myRunnable,DELAYED_TIME);
}
class MyRunnable implements Runnable{
@Override
public void run() {
progress++;
if(progress<=100){
progressbar.setProgress(progress);
numberProgressBar.setProgress(progress);
handler.postDelayed(myRunnable,DELAYED_TIME);
}else{
handler.removeCallbacksAndMessages(myRunnable);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(myRunnable);
}
}
源码地址:https://github.com/Mersens/CircleNumberProgressBar
使用方法
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
Add the dependency
dependencies {
compile 'com.github.Mersens:CircleNumberProgressBar:1.0'
}