概述

今天继续学习自定义View,今天的内容是自定义进度条,我们分为水平进度条和圆形进度条

水平进度条

先看效果图

自定义进度条_ide

首先我们要做到就是分析需要哪些自定义属性,然后我们在attrs文件中需要去声明这些属性,我们很容易分析出来有如下属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ProgressBarWithProgress">
<!--未到达进度条颜色-->
<attr name="progress_unreach_color" format="color"/>
<!--未到达进度条高度-->
<attr name="progress_unreach_height" format="dimension"/>
<!--已到达进度条颜色-->
<attr name="progress_reach_color" format="color"/>
<!--已到达进度条高度-->
<attr name="progress_reach_height" format="dimension"/>
<!--进度文字颜色-->
<attr name="progress_text_color" format="color"/>
<!--进度文字大小-->
<attr name="progress_text_size" format="dimension"/>
<!--文字和进度条间距-->
<attr name="progress_text_margin" format="dimension"/>
</declare-styleable>

</resources>

分析完了所有的属性之后,我们开使绘制进度条,我们可以把进度条分为三段,已到达的进度条,文字,未到达的进度条。接下来我们在代码中分别进行绘制。

我们先来看一张图,

自定义进度条_进度条_02

我们依据上面划分的原则来进行绘制

public class ProgressBarWithProgress extends ProgressBar {
//默认属性的值
private static final int DEFAULT_REACH_COLOR = 0XFF00FF;
private static final int DEFAULT_REACH_HEIGHT = 5;
private static final int DEFAULT_UNREACH_COLOR = 0XFF00;
private static final int DEFAULT_UNREACH_HEIGHT = 4;
private static final int DEFAULT_TEXT_COLOR = 0X00FF;
private static final int DEFAULT_TEXT_SIZE = 10;
private static final int DEFAULT_TEXT_MARGIN = 8;
private int mReachHeight = dp2px(DEFAULT_REACH_HEIGHT);
private int mUnReachHeight = dp2px(DEFAULT_UNREACH_HEIGHT);
private int mTextSize = sp2px(DEFAULT_TEXT_SIZE);
private int mTextMargin = dp2px(DEFAULT_TEXT_MARGIN);

//实际的宽度,减去padding
private int mReadWidth;
//画笔
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int reachColor;
private final float reachHeight;
private final int unReachColor;
private final float unReachHeight;
private final int textColor;
private final float textSize;
private final float textMargin;

public ProgressBarWithProgress(Context context, AttributeSet attrs) {
super(context, attrs);
//将自定义的属性封装到typedArray中
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressBarWithProgress);
reachColor = typedArray.getColor(R.styleable.ProgressBarWithProgress_progress_reach_color, DEFAULT_REACH_COLOR);
reachHeight = typedArray.getDimension(R.styleable.ProgressBarWithProgress_progress_reach_height, mReachHeight);
unReachColor = typedArray.getColor(R.styleable.ProgressBarWithProgress_progress_unreach_color, DEFAULT_UNREACH_COLOR);
unReachHeight = typedArray.getDimension(R.styleable.ProgressBarWithProgress_progress_unreach_height, mUnReachHeight);
textColor = typedArray.getColor(R.styleable.ProgressBarWithProgress_progress_text_color, DEFAULT_TEXT_COLOR);
textSize = typedArray.getDimension(R.styleable.ProgressBarWithProgress_progress_text_size,mTextSize);
textMargin = typedArray.getDimension(R.styleable.ProgressBarWithProgress_progress_text_margin,mTextMargin);
typedArray.recycle();
//设置画笔的所画文字大小,必须设置,否则无法获取文字高度
mPaint.setTextSize(mTextSize);
}

@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = measureHeight(heightMeasureSpec);//进度条的高度
int width = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width,height);
//获取实际的宽度,减去左右两边的间距
mReadWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}

@Override
protected void onDraw(Canvas canvas) {
canvas.save();
//平移画布,便于绘制
canvas.translate(getPaddingLeft(),getHeight()/2);
String text = getProgress() + "%";
//是否需要绘制未到达的进度条
boolean needDrawUnReach = true;
//当前走过的进度比率
float ratio = getProgress()*1.0f/getMax();
//文字的宽度
int textWidth = (int) mPaint.measureText(text);
//当前进度所占总进度的宽度,这里我们以文字的结束位置为标准计算
int progressX = (int) (ratio*mReadWidth);
//已到达进度条结束点的坐标,我们这里的进度
float endX = progressX-textMargin-textWidth;
if(progressX>=mReadWidth){//progressX最大不能超过进度条宽度
progressX = mReadWidth;
needDrawUnReach = false;
}
if(endX > 0){
mPaint.setColor(reachColor);
mPaint.setStrokeWidth(reachHeight);
//绘制已到达的进度条
canvas.drawLine(0,0,endX,0,mPaint);
}
//画文字
mPaint.setColor(textColor);
mPaint.setTextSize(textSize);
//由于坐标移动到进度条的中间,二画文字的y坐标默认是文字的左下角,所以
//y的坐标是文字高度的一半,并且是小于0
int y = (int) (-(mPaint.descent() + mPaint.ascent())/2);
canvas.drawText(text,progressX-textWidth,y,mPaint);
//画未达到的进度条
if(needDrawUnReach){
Float start = progressX+textMargin;
mPaint.setColor(unReachColor);
mPaint.setStrokeWidth(unReachHeight);
canvas.drawLine(start,0,mReadWidth,0,mPaint);
}
canvas.restore();//恢复画布
}

/**
* 设置进度条的高度
* @param heightMeasureSpec
* @return
*/
public int measureHeight(int heightMeasureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if(specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
int textHeight = (int) (mPaint.descent() - mPaint.ascent());
//进度条的高度为三者最大的高度
result = getPaddingTop()+getPaddingBottom()+Math.max(Math.max(mReachHeight,mUnReachHeight),Math.abs(textHeight));
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(specSize,result);
}
}
return result;
}

private int dp2px(int dpValue){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpValue,getResources().getDisplayMetrics());
}
private int sp2px(int spValue){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,spValue,getResources().getDisplayMetrics());
}
}

接下来我们要做的就是在代码中不断增加进度条的值

public class MainActivity extends Activity{

private ProgressBarWithProgress progressBar;
public static final int MSG_UPDATE = 0X110;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int currentProgress = progressBar.getProgress();
progressBar.setProgress(++currentProgress);
if(currentProgress>= progressBar.getMax()){
mHandler.removeMessages(MSG_UPDATE);
}else{
mHandler.sendEmptyMessageDelayed(MSG_UPDATE,200);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = (ProgressBarWithProgress) findViewById(R.id.progress);
mHandler.sendEmptyMessage(MSG_UPDATE);
}

最后看看布局文件是如何写的

<com.example.androidheros.ProgressBarWithProgress
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom:progress_reach_color="#ff00ff"
custom:progress_reach_height="5dp"
custom:progress_text_color="#000000"
custom:progress_text_size="8sp"
android:padding="10dp"
custom:progress_unreach_color="#00ff00"
custom:progress_unreach_height="3dp"
custom:progress_text_margin="5dp"
android:progress="0"
style="@android:style/Widget.ProgressBar.Horizontal"
>
</com.example.androidheros.ProgressBarWithProgress>

圆形进度条

静图

自定义进度条_进度条_03

动图

自定义进度条_进度条_04

public class RoundProgressBar extends ProgressBar {
//默认属性的值
private static final int DEFAULT_REACH_COLOR = 0XFF00FF;
private static final int DEFAULT_REACH_HEIGHT = 5;
private static final int DEFAULT_UNREACH_COLOR = 0XFF00;
private static final int DEFAULT_UNREACH_HEIGHT = 4;
private static final int DEFAULT_TEXT_COLOR = 0X00FF;
private static final int DEFAULT_TEXT_SIZE = 10;
private static final int DEFAULT_TEXT_MARGIN = 8;
private static final int DEFAULT_RADIUS = 30;
private int mReachHeight = dp2px(DEFAULT_REACH_HEIGHT);
private int mUnReachHeight = dp2px(DEFAULT_UNREACH_HEIGHT);
private int mTextSize = sp2px(DEFAULT_TEXT_SIZE);
private int mRadius = dp2px(DEFAULT_RADIUS);

private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int reachColor;
private final float reachHeight;
private final int unReachColor;
private final float unReachHeight;
private final int textColor;
private final float textSize;
private float radius;
private float maxPaintWidth;

public RoundProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
//将自定义的属性封装到typedArray中
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
reachColor = typedArray.getColor(R.styleable.RoundProgressBar_progress_reach_color, DEFAULT_REACH_COLOR);
reachHeight = typedArray.getDimension(R.styleable.RoundProgressBar_progress_reach_height, mReachHeight);
unReachColor = typedArray.getColor(R.styleable.RoundProgressBar_progress_unreach_color, DEFAULT_UNREACH_COLOR);
unReachHeight = typedArray.getDimension(R.styleable.RoundProgressBar_progress_unreach_height, mUnReachHeight);
textColor = typedArray.getColor(R.styleable.RoundProgressBar_progress_text_color, DEFAULT_TEXT_COLOR);
textSize = typedArray.getDimension(R.styleable.RoundProgressBar_progress_text_size,mTextSize);
radius = typedArray.getDimension(R.styleable.RoundProgressBar_progress_radius, mRadius);
typedArray.recycle();
}

@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//取画笔的宽度为两个进度条宽度的最大值
maxPaintWidth = Math.max(reachHeight,unReachHeight);
//默认四个padding一致,在没有设置进度条的宽高时使用这个值作为宽高的计算标准
int espect = (int) (radius*2+maxPaintWidth+getPaddingRight()+getPaddingLeft());
//更具测量模式获取对应的尺寸,实现细节可以参考源码
int width = resolveSize(espect,widthMeasureSpec);
int height = resolveSize(espect,heightMeasureSpec);
//圆形进度条的实际宽度,取高和宽最小值,因为进度条是一个圆形
int realWidth = Math.min(width,height);
//圆形未到达进度条的半径,从涂上我们可以看出,因为画笔的一般在圆形的外面
radius = (realWidth - getPaddingLeft() - getPaddingRight()-maxPaintWidth)/2;
//保存测量值,这步必须做
setMeasuredDimension(realWidth,realWidth);
}

@Override
protected synchronized void onDraw(Canvas canvas) {
mPaint.setTextSize(textSize);
//中间显示的文字
String text = getProgress()+"%";
//文字宽度
float textWidht = mPaint.measureText(text);
//文字的高度,注意,这样写的前提是给Pain设置了textSize
float textHeight = (mPaint.descent()-mPaint.ascent());
canvas.save();
//未到达进度。注意这个圆形的半径是包含画笔宽度的一半的,可以参照上面那张图
mPaint.setStyle(Paint.Style.STROKE);
//平移画布,目的是为了方便画圆
canvas.translate(getPaddingLeft()+maxPaintWidth/2,getPaddingTop()+maxPaintWidth/2);
mPaint.setColor(unReachColor);
mPaint.setStrokeWidth(unReachHeight);
canvas.drawCircle(radius,radius,radius,mPaint);
//画已到达进度
mPaint.setColor(reachColor);
mPaint.setStrokeWidth(reachHeight);
//根据当前进度计算弧度
float sweepAngle = getProgress()*1.0f/getMax()*360;
RectF rectF = new RectF(0,0,radius*2,radius*2);
//从-90度开始画圆,
canvas.drawArc(rectF,-90,sweepAngle+90/getMax()*360,false,mPaint);
//画文字
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(textColor);
canvas.drawText(text,radius-textWidht/2,radius+textHeight/4,mPaint);
canvas.restore();
}

private int dp2px(int dpValue){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpValue,getResources().getDisplayMetrics());
}
private int sp2px(int spValue){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,spValue,getResources().getDisplayMetrics());
}
}

改变进度条的代码和水平进度条中的一样,这里就不贴出来了,请参照之前的代码。