Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
Effect & Use
Legends are as follows:
- Change the start point of a read bar to the positive Y-axis
- Disappearing read bar
- Normal read article
Use:
- 1 Add controls to 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" />
Copy the code
- 2 Start the countdown in the code
Val trackBar = findViewById<ProgressTrackBar>(R.i.progress_track_bar) trackbar.setStartAngle (-90f) // Start reading the bar from -90 degrees TrackBar. SetOnProgressListener {/ / progress callback the d (" ProgressTrackBar ", "progress is $it")}. TrackBar startTask (0) {/ / start time, Log.d("ProgressTrackBar", FindViewById <ProgressTrackBar>(r.id.progress_track_bar2).starttask (0) // Start the timer from 20 findViewById<ProgressTrackBar>(R.id.progress_track_bar3).startTask(20)Copy the code
Ideas & Coding
The core idea is one: draw the original ring. We will draw two circles, one full circle on the lower layer for the background color, and one circle on the upper layer for the progress. The point is to calculate the arc radians.
If the current progress is current and the maximum progress is Max, then the current arc progress is :(current/ Max)*360, then we directly call:
// startAngle: The starting Angle from which to draw. The direction is calculated clockwise. // sweepAngle: (current/ Max)x360 // false: DrawArc (oval, startAngle, sweepAngle, false, mPaint);Copy the code
I can draw the corresponding arc.
So, here’s how:
// Draw the bottom layer: circle mPaint. SetColor (firstLayerColor); canvas.drawCircle(x, y, radius, mPaint); // Draw the top layer: the arc mPaint. SetColor (secondLayerColor); float sweepAngle = (currentProgress / maxProgress) * 360; canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);Copy the code
We draw a circle with the bottom color, then draw an arc with the top color, and then constantly redraw to get the desired effect.
But what if we wanted the arc to get shorter and shorter as we progressed? Take the second effect of the sample diagram. To put it bluntly, the upper layer becomes smaller over time until it disappears. How can it be realized?
In fact, the more time it takes, the less radians it is, so let’s just subtract, let’s use max-current as the read progress, so as time goes by, the progress gets smaller and smaller.
Some people say, “This is not right, so that the max-current is smaller and smaller, so that the arc of the drawing is shorter and shorter, and finally it completely leaks out of the bottom layer, giving the impression that it is read backwards.” Exactly. So, we only draw one layer, and we draw the arc with the bottom color! In this way, as time goes by, the radians get smaller and smaller, because the arcs are drawn with lower layers, so visually, there are fewer and fewer layers. The feeling is that the top is getting bigger and bigger to cover the bottom.
The logic is as follows:
// Draw the remaining radians with the underlying color mPaint. SetColor (firstLayerColor); float leaveAngle = ((maxProgress - currentProgress) / maxProgress) * 360; canvas.drawArc(oval, startAngle, leaveAngle, false, mPaint);Copy the code
As you can see, there is only one layer drawn here, and as time goes by, the arc gets shorter and shorter, giving the impression that the arc disappears. The effect of the second arc in the example diagram is achieved.
The overall code is as follows:
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; Private int speed = DEFAULT_SPEED; Ms private int progressWidth = PROGRESS_WIDTH; // Progress bar 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) {// Draw the remaining radians mPaint. SetColor (firstLayerColor) with the bottom color; float leaveAngle = ((maxProgress - currentProgress) / maxProgress) * 360; canvas.drawArc(oval, startAngle, leaveAngle, false, mPaint); } else {// Draw the bottom layer mPaint. SetColor (firstLayerColor); canvas.drawCircle(x, y, radius, mPaint); // Draw the upper layer 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); }}Copy the code
conclusion
The main idea is this: if the upper layer wants to cover the lower layer with transparency, it is impossible, so it is better to draw the lower layer with the relative value of the upper layer.