消失性进度条

10/27/2021 AndroidView

# 效果&使用

效果

图例分别为:

  • 修改读条起点为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" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 思路&编码

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

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

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

就能绘制出对应的圆弧。

所以,我们这样:

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

// 绘制上层: 圆弧
mPaint.setColor(secondLayerColor);
float sweepAngle = (currentProgress / maxProgress) * 360;
canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);
1
2
3
4
5
6
7
8

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

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

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

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

逻辑如下:

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

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

整体代码如下:

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);
    }
}
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

# 总结

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

Last Updated: 1/27/2022, 2:50:43 PM