注册

消失性进度条

效果&使用


效果


图例分别为:



  • 修改读条起点为y轴正方向
  • 消失性读条
  • 正常读条

使用:



  • 1 在xml中添加控件

<com.lloydfinch.ProgressTrackBar
android:id="@+id/progress_track_bar"
android:layout_width="62dp"
android:layout_height="62dp"
app:p_second_color="#E91E63"
app:p_width="3dp" />

<com.lloydfinch.ProgressTrackBar
android:id="@+id/progress_track_bar2"
android:layout_width="62dp"
android:layout_height="62dp"
app:p_first_color="#18B612"
app:p_second_color="#00000000"
app:p_width="3dp" />

<com.lloydfinch.ProgressTrackBar
android:id="@+id/progress_track_bar3"
android:layout_width="62dp"
android:layout_height="62dp"
app:p_first_color="#ffd864"
app:p_second_color="#1C3F7C"
app:p_width="3dp" />


  • 2 在代码中启动倒计时

val trackBar = findViewById<ProgressTrackBar>(R.id.progress_track_bar)
trackBar.setStartAngle(-90F) // 从-90度开始读条
trackBar.setOnProgressListener { // 进度回调
Log.d("ProgressTrackBar", "progress is $it")
}
trackBar.startTask(0) { // 开始计时,传入读条结束的回调
Log.d("ProgressTrackBar", "progress run finish")
}

// 从0开始计时
findViewById<ProgressTrackBar>(R.id.progress_track_bar2).startTask(0)

// 从20开始计时
findViewById<ProgressTrackBar>(R.id.progress_track_bar3).startTask(20)

思路&编码


核心思路就一个: 画原环。我们要画两个圆环,一个下层的完整圆环作为底色,一个上层的圆弧作为进度。重点就是计算圆弧弧度的问题了。


假设当前进度是current,最大进度是max,那么当前圆弧进度就是:(current/max)*360,然后我们直接调用:


// oval: 放置圆弧的矩形
// startAngle: 开始绘制的起点角度,方向是顺时针计算的。0就x正半轴,90就是y轴负半轴
// sweepAngle: 要绘制的圆弧的弧度,就是上述: (current/max)x360
// false: 表示不连接到圆心,表示绘制一个圆弧
canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);

就能绘制出对应的圆弧。


所以,我们这样:


// 绘制下层: 圆形
mPaint.setColor(firstLayerColor);
canvas.drawCircle(x, y, radius, mPaint);

// 绘制上层: 圆弧
mPaint.setColor(secondLayerColor);
float sweepAngle = (currentProgress / maxProgress) * 360;
canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);

我们先用下层颜色绘制一个圆形,然后用上层颜色绘制个圆弧,然后不断触发重绘,就能得到想要的效果。


但是,如果我们想要的是: 随着进度变大,圆弧越来越短呢?比如示例图的第二个效果。说白了就是让上层随着时间流逝而变小,直到消失,怎么实现呢?


其实,说白了就是时间越长,弧度越小,我们做减法即可,我们用(max-current)来作为已读进度,这样随着时间流逝,进度就越来越小。


有人说,这样不对啊,这样(max-current)不就越读越小了吗,这样画出来的弧度就越来越短了,最后完全漏出了底层,给人的感觉是倒着读的。没错,所以,我们只绘制一层,我们用下层颜色来绘制圆弧!这样,随着时间流逝,弧度越来越小,因为圆弧是用下层颜色绘制的,所以视觉上就是: 下层越来越少。给人的感觉就是: 上层越来越大以至于盖住了下层。


逻辑如下:


// 用下层颜色 绘制 剩下的弧度
mPaint.setColor(firstLayerColor);
float leaveAngle = ((maxProgress - currentProgress) / maxProgress) * 360;
canvas.drawArc(oval, startAngle, leaveAngle, false, mPaint);

可以看到,这里只绘制一层,随着时间流逝,圆弧越来越短,给人的感觉就是: 圆弧消失。就达到了示例图中 第二个圆弧的效果。


整体代码如下:


public class ProgressTrackBar extends View {


private static final int DEFAULT_FIRST_COLOR = Color.WHITE;
private static final int DEFAULT_SECOND_COLOR = Color.parseColor("#FFA12F");

private static final int PROGRESS_WIDTH = 6;
private static final float MAX_PROGRESS = 360F;
private static final int DEFAULT_SPEED = 1;

private Paint mPaint;
private float startAngle = 0;
private int firstLayerColor = DEFAULT_FIRST_COLOR;
private int secondLayerColor = DEFAULT_SECOND_COLOR;
private final RectF oval = new RectF(); // 圆形轨迹
private float maxProgress = MAX_PROGRESS; // 最大进度:ms
private float currentProgress = 0F; // 当前进度:ms
private int speed = DEFAULT_SPEED; // 速度(多长时间更新一次UI):ms
private int progressWidth = PROGRESS_WIDTH; // 进度条宽度

private OnProgressFinished onProgressFinished;

private Handler taskHandler;
private OnProgress runnable; //进度回调

// 顶层颜色是否是透明
private boolean isSecondColorTransparent = false;

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

public ProgressTrackBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressTrackBar);
firstLayerColor = typedArray.getColor(R.styleable.ProgressTrackBar_p_first_color, DEFAULT_FIRST_COLOR);
secondLayerColor = typedArray.getColor(R.styleable.ProgressTrackBar_p_second_color, DEFAULT_SECOND_COLOR);
startAngle = typedArray.getFloat(R.styleable.ProgressTrackBar_p_start, 0F);
progressWidth = typedArray.getDimensionPixelSize(R.styleable.ProgressTrackBar_p_width, PROGRESS_WIDTH);
maxProgress = typedArray.getDimension(R.styleable.ProgressTrackBar_p_max_progress, MAX_PROGRESS);

typedArray.recycle();

init();
}

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

private void init() {
refresh();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(progressWidth);
}

public void setFirstLayerColor(int firstLayerColor) {
this.firstLayerColor = firstLayerColor;
}

public void setSecondLayerColor(int secondLayerColor) {
this.secondLayerColor = secondLayerColor;
refresh();
}

public void setMaxProgress(float maxProgress) {
this.maxProgress = maxProgress;
}

public void setSpeed(int speed) {
this.speed = speed;
}

public void setStartAngle(float startAngle) {
this.startAngle = startAngle;
}

public void setProgressWidth(int progressWidth) {
this.progressWidth = progressWidth;
}

public void setOnProgressListener(OnProgress runnable) {
this.runnable = runnable;
}

public void setOnProgressFinished(OnProgressFinished onProgressFinished) {
this.onProgressFinished = onProgressFinished;
}

private void initTask() {
taskHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (currentProgress < maxProgress) {
currentProgress += speed;
postInvalidate();
if (runnable != null) {
runnable.onProgress(currentProgress);
}
taskHandler.sendEmptyMessageDelayed(0, speed);
} else {
stopTask();
}
}
};
}

private void refresh() {
isSecondColorTransparent = (secondLayerColor == Color.parseColor("#00000000"));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int x = getWidth() >> 1;
int y = getHeight() >> 1;
int center = Math.min(x, y);
int radius = center - progressWidth;

int left = x - radius;
int top = y - radius;
int right = x + radius;
int bottom = y + radius;
oval.set(left, top, right, bottom);

// 这里需要处理一下上层是透明的情况
if (isSecondColorTransparent) {
// 用下层颜色 绘制 剩下的弧度
mPaint.setColor(firstLayerColor);
float leaveAngle = ((maxProgress - currentProgress) / maxProgress) * 360;
canvas.drawArc(oval, startAngle, leaveAngle, false, mPaint);
} else {
// 绘制下层
mPaint.setColor(firstLayerColor);
canvas.drawCircle(x, y, radius, mPaint);

// 绘制上层
mPaint.setColor(secondLayerColor);
float sweepAngle = (currentProgress / maxProgress) * 360;
canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);
}
}

public void startTask(int progress) {
currentProgress = progress;
initTask();
taskHandler.sendEmptyMessage(0);
}

public void startTask(int progress, OnProgressFinished onProgressFinished) {
this.onProgressFinished = onProgressFinished;
currentProgress = progress;
initTask();
taskHandler.sendEmptyMessage(0);
}

public void stopTask() {
if (onProgressFinished != null) {
onProgressFinished.onFinished();
}
if (taskHandler != null) {
taskHandler.removeCallbacksAndMessages(null);
}
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopTask();
}

public interface OnProgressFinished {
void onFinished();
}

public interface OnProgress {
void onProgress(float progress);
}
}

总结


核心思路就一个: 如果上层要用透明盖住下层,这是不可能的,所以不如用上层的相对值去绘制下层


作者:奔波儿灞取经
链接:https://juejin.cn/post/7021448361488154631
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册