UI-进度条

自定义酷炫的进度条

效果图:【目前没有接入外部数据,是自身有的进度在跳】

img

源码地址:https://gitee.com/tu_erhongjiang/android-progress-bar

实现步骤

绘制背景圆形矩形

画出一个圆形矩形,RectF里面传递的是矩形左上角和右下角的xy坐标,用来确定矩形的位置和大小,然后在内部画出一个原型矩形:

1
2
3
4
5
private void drawBackground(Canvas canvas){
//圆角矩形
RectF rectF = new RectF(padding, padding, mWidth - padding, mHeight - padding);
canvas.drawRoundRect(rectF, round, round, mPaintRoundRect);
}

绘制进度

里面的进度条也是圆形矩形,不过进度条的画笔是实心的。内部进度条矩形的大小略小于外面的矩形(这里是减掉strokeWidth)

1
2
3
4
5
6
private void drawProgress(Canvas canvas){
if (process!=0){
RectF rectProgress = new RectF(padding + strokeWidth, padding + strokeWidth, process, mHeight - padding - strokeWidth);//内部进度条
canvas.drawRoundRect(rectProgress, round, round, mPaint);
}
}

绘制文字

getWidth()/2是中间位置的x坐标,但从这里开始绘制文字的话不能实现居中的效果,需要计算出文字的长度再把文字整体左移。mTxtWidth/2是文字的中心位置,也就是把文字的中心位置移到矩形中心位置就可以实现居中的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void updateText(Canvas canvas) {
String finishedText = getResources().getString(R.string.finished);
String defaultText = getResources().getString(R.string.defaultText);
int percent = (int) (process / (mWidth - padding - strokeWidth) * 100);
Paint.FontMetrics fm = mPaintText.getFontMetrics();
int mTxtWidth = (int) mPaintText.measureText(finishedText, 0, defaultText.length());
int mTxtHeight = (int) Math.ceil(fm.descent - fm.ascent);
int x = getWidth() / 2 - mTxtWidth / 2; //文字在画布中的x坐标
int y = getHeight() / 2 + mTxtHeight / 4; //文字在画布中的y坐标
if (percent < 100) {
canvas.drawText(percent + "%", x, y, mPaintText);
} else {
canvas.drawText(finishedText, x, y, mPaintText);
}
}

加入动画

让进度条动起来,这里用到的是属性动画中的ValueAnimator,这种动画不能直接修改view,类似于timer,需要我们传递一个数值范围和执行时间。比如说3秒内从1加到100,然后在接口回调时拿到当前的进度,执行view的invalidate()方法属性UI

1
2
3
4
5
6
7
8
9
10
11
//属性动画
public void start() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mWidth - padding - strokeWidth);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(animation -> {
process = (float) animation.getAnimatedValue();
invalidate();
});
valueAnimator.start();
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

import androidx.annotation.Nullable;

public class HorizontalProgressView extends View {

private Paint mPaint;
private Paint mPaintRoundRect;
private Paint mPaintText;
private int mWidth;
private int mHeight;
private int padding = 5;
private int strokeWidth = 8;
private int textSize = 15;
private long duration = 3500;
private int round;
private float process;

public HorizontalProgressView(Context context) {
super(context);
init();
}

public HorizontalProgressView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}

public HorizontalProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

//初始化画笔
private void init(){
mPaintRoundRect = new Paint();//圆角矩形
mPaintRoundRect.setColor(getResources().getColor(R.color.back));//圆角矩形颜色
mPaintRoundRect.setAntiAlias(true);// 抗锯齿效果
mPaintRoundRect.setStyle(Paint.Style.STROKE);//设置画笔样式
mPaintRoundRect.setStrokeWidth(strokeWidth);//设置画笔宽度

mPaint = new Paint();
mPaint.setColor(getResources().getColor(R.color.inner));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(strokeWidth);

mPaintText = new Paint();
mPaintText.setAntiAlias(true);
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setColor(getResources().getColor(R.color.back));
mPaintText.setTextSize(sp2px(textSize));
}

public void setPadding(int padding) {
this.padding = padding;
}

public void setStrokeWidth(int strokeWidth) {
this.strokeWidth = strokeWidth;
}

public void setTextSize(int textSize) {
this.textSize = textSize;
}

public void setDuration(long duration) {
this.duration = duration;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//MeasureSpec.EXACTLY,精确尺寸
if (widthSpecMode == MeasureSpec.EXACTLY || widthSpecMode == MeasureSpec.AT_MOST) {
mWidth = widthSpecSize;
} else {
mWidth = 0;
}
//MeasureSpec.AT_MOST,最大尺寸,只要不超过父控件允许的最大尺寸即可,MeasureSpec.UNSPECIFIED未指定尺寸
if (heightSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.UNSPECIFIED) {
mHeight = defaultHeight();
} else {
mHeight = heightSpecSize;
}
//设置控件实际大小
round = mHeight / 2;//圆角半径
setMeasuredDimension(mWidth, mHeight);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);//绘制背景矩形
drawProgress(canvas);//绘制进度
updateText(canvas);//处理文字
}

private void drawBackground(Canvas canvas){
RectF rectF = new RectF(padding, padding, mWidth - padding, mHeight - padding);//圆角矩形
canvas.drawRoundRect(rectF, round, round, mPaintRoundRect);
}

private void drawProgress(Canvas canvas){
if (process!=0){
RectF rectProgress = new RectF(padding + strokeWidth, padding + strokeWidth, process, mHeight - padding - strokeWidth);//内部进度条
canvas.drawRoundRect(rectProgress, round, round, mPaint);
}
}

private void updateText(Canvas canvas) {
String finishedText = getResources().getString(R.string.finished);
String defaultText = getResources().getString(R.string.defaultText);
int percent = (int) (process / (mWidth - padding - strokeWidth) * 100);
Paint.FontMetrics fm = mPaintText.getFontMetrics();
int mTxtWidth = (int) mPaintText.measureText(finishedText, 0, defaultText.length());
int mTxtHeight = (int) Math.ceil(fm.descent - fm.ascent);
int x = getWidth() / 2 - mTxtWidth / 2; //文字在画布中的x坐标
int y = getHeight() / 2 + mTxtHeight / 4; //文字在画布中的y坐标
if (percent < 100) {
canvas.drawText(percent + "%", x, y, mPaintText);
} else {
canvas.drawText(finishedText, x, y, mPaintText);
}
}

//属性动画
public void start() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mWidth - padding - strokeWidth);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(animation -> {
process = (float) animation.getAnimatedValue();
invalidate();
});
valueAnimator.start();
}

private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
getResources().getDisplayMetrics());
}

//进度条默认高度,未设置高度时使用
private int defaultHeight() {
float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (20 * scale + 0.5f * (20 >= 0 ? 1 : -1));
}

}

网上库

https://blog.csdn.net/shenggaofei/article/details/101721895