How to Create a Circular Progress Indicator for a Count Down Timer

CountDown Timer with CircularProgressIndicator

Your progress isn't calculated correctly.

progressIndicator.progress = (millisUntilFinished / 1000).toInt()

If millisUntilFinished == 59000, you will end up with a progress of 59. You should be calculating a percentage here instead of this.

How to create a circular progress indicator for a count down timer

You can do it using CAShapeLayer and animating the stroke end as follow:

update Xcode 9 • Swift 4

define the time left

let timeLeftShapeLayer = CAShapeLayer()
let bgShapeLayer = CAShapeLayer()
var timeLeft: TimeInterval = 60
var endTime: Date?
var timeLabel = UILabel()
var timer = Timer()
// here you create your basic animation object to animate the strokeEnd
let strokeIt = CABasicAnimation(keyPath: "strokeEnd")

define a method to create de UIBezierPath

startAngle at -90˚ and endAngle 270

func drawBgShape() {
bgShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius:
100, startAngle: -90.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: true).cgPath
bgShapeLayer.strokeColor = UIColor.white.cgColor
bgShapeLayer.fillColor = UIColor.clear.cgColor
bgShapeLayer.lineWidth = 15
view.layer.addSublayer(bgShapeLayer)
}
func drawTimeLeftShape() {
timeLeftShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius:
100, startAngle: -90.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: true).cgPath
timeLeftShapeLayer.strokeColor = UIColor.red.cgColor
timeLeftShapeLayer.fillColor = UIColor.clear.cgColor
timeLeftShapeLayer.lineWidth = 15
view.layer.addSublayer(timeLeftShapeLayer)
}

add your Label

func addTimeLabel() {
timeLabel = UILabel(frame: CGRect(x: view.frame.midX-50 ,y: view.frame.midY-25, width: 100, height: 50))
timeLabel.textAlignment = .center
timeLabel.text = timeLeft.time
view.addSubview(timeLabel)
}

at viewDidload set the endTime and add your CAShapeLayer to your view:

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.94, alpha: 1.0)
drawBgShape()
drawTimeLeftShape()
addTimeLabel()
// here you define the fromValue, toValue and duration of your animation
strokeIt.fromValue = 0
strokeIt.toValue = 1
strokeIt.duration = timeLeft
// add the animation to your timeLeftShapeLayer
timeLeftShapeLayer.add(strokeIt, forKey: nil)
// define the future end time by adding the timeLeft to now Date()
endTime = Date().addingTimeInterval(timeLeft)
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}

when updating the time

@objc func updateTime() {
if timeLeft > 0 {
timeLeft = endTime?.timeIntervalSinceNow ?? 0
timeLabel.text = timeLeft.time
} else {
timeLabel.text = "00:00"
timer.invalidate()
}
}

you can use this extension to convert the degrees to radians and display time

extension TimeInterval {
var time: String {
return String(format:"%02d:%02d", Int(self/60), Int(ceil(truncatingRemainder(dividingBy: 60))) )
}
}
extension Int {
var degreesToRadians : CGFloat {
return CGFloat(self) * .pi / 180
}
}

Sample project

Circular Progress Bar ( for a countdown timer )

I found this example very good: http://mrigaen.blogspot.it/2013/12/create-circular-progress-bar-in-android.html

So I created my progress bar in this way

    android:id="@+id/barTimer"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:progressDrawable="@drawable/circular_progress_bar" />

Then I made a function for the countdown where:

private void startTimer(final int minuti) {
countDownTimer = new CountDownTimer(60 * minuti * 1000, 500) {
// 500 means, onTick function will be called at every 500 milliseconds

@Override
public void onTick(long leftTimeInMilliseconds) {
long seconds = leftTimeInMilliseconds / 1000;
int barVal= (barMax) - ((int)(seconds/60*100)+(int)(seconds%60));
barTimer.setProgress(barVal);
textTimer.setText(String.format("%02d", seconds/60) + ":" + String.format("%02d", seconds%60));
// format the textview to show the easily readable format

}
@Override
public void onFinish() {
if(textTimer.getText().equals("00:00")){
textTimer.setText("STOP");
}
else{
textTimer.setText("2:00");
}
}
}.start();

}

UPDATE

private void startTimer(final int minuti) {
countDownTimer = new CountDownTimer(60 * minuti * 1000, 500) {
// 500 means, onTick function will be called at every 500 milliseconds

@Override
public void onTick(long leftTimeInMilliseconds) {
long seconds = leftTimeInMilliseconds / 1000;
barTimer.setProgress((int)seconds);
textTimer.setText(String.format("%02d", seconds/60) + ":" + String.format("%02d", seconds%60));
// format the textview to show the easily readable format

}
@Override
public void onFinish() {
if(textTimer.getText().equals("00:00")){
textTimer.setText("STOP");
}
else{
textTimer.setText("2:00");
barTimer.setProgress(60*minuti);
}
}
}.start();

}

Circular progress bar with countdown timer in Android

Your scaling factor is not correct and hence your maxProgess and setProgess values are also incorrect.There are two ways to fix this.

1.Set seconds = leftTimeInMilliseconds / 600 and not 1000.

Let's say X% = 1 second then 100% = 60 seconds,then X% = 100/60 seconds but we are calculating in milliseconds
hence X% = 100/(60*1000) = 1/600 milliseconds.
Also we have set 60 seconds for 100% hence multiply your setProgess and setMax by a factor 10.Since 600 seconds is 10 minutes.

barTimer.setProgress(1000);
barTimer.setMax(1000);

long seconds = leftTimeInMilliseconds / 600;

2.Similar to above rather than dividing by 600 divide 6000.
seconds = leftTimeInMilliseconds / 6000;

Let's say X% = 1 second then 100% = 600 seconds(10 mins),then X% = 100/600 seconds but we are calculating in milliseconds
hence X% = 100/(60*1000) = 1/6000 milliseconds.

Now since we are directly calculating for 10 mins no need to multiply your setProgess and setMax by any factor.

barTimer.setProgress(100);
barTimer.setMax(100);

long seconds = leftTimeInMilliseconds / 6000;

Also I would suggest setting countDownInterval to 1 sec rather 0.5 sec.

Hope this helps.
Please mark the answer as accepted if it helps.

How to make a circular progress countdown timer?

Here is the demo: https://codepen.io/creativedev/pen/ERwGej

var counter = document.getElementById('counter').getContext('2d');
var no = 10;
var pointToFill = 4.72;
var cw = counter.canvas.width;
var ch = counter.canvas.height;
var diff;

function fillCounter() {
diff = ((no / 10) * Math.PI * 2 * 10);
counter.clearRect(0, 0, cw, ch);
counter.lineWidth = 15;
counter.fillStyle = '#000';
counter.strokeStyle = '#F5E0A9';
counter.textAlign = 'center';
counter.font = "25px monospace";
console.log(no);
counter.fillText(no + 'sec', 100, 110);
counter.beginPath();
console.log(diff)
counter.arc(100, 100, 90, pointToFill, diff / 10 + pointToFill);
counter.stroke();

if (no == 0) {
clearTimeout(fill);
}
no--;
}

var fill = setInterval(fillCounter, 1000);

How to Create a circular progressbar with CountDown in Android

For these type of progress bar,
try one of these two libraries that are on GitHub:

https://github.com/Todd-Davies/ProgressWheel

https://github.com/f2prateek/progressbutton?source=c

CountDownTImer With Circular Progressbar in Android

There are a few libraries the best in my opinion is CircularProgress

Count down timer with circular progress bar

Below is a sample snippet for the countdown timer with a circular progress bar that changes color as the value comes down.

Basically what we are doing is the follows: (refer inline comments in code for more details)

  • 4 additional div are absolutely positioned on top of the parent. Each represents a quadrant.
  • Initially the skew angle on all of them is 0 degree, so they are all fully visible and cover the entire parent. This hides the box-shadow of the parent and thus makes it look like a solid circle.
  • At every iteration, we modify the skew angle of each quadrant (div) such that the quadrants eventually become invisible one by one and thus revealing the box-shadow of parent.
  • The quadrants become invisible when the skew angle reaches +/- 90 degrees and so at each iteration the angle is calculated as (90deg / no. of steps covered in that quadrant).
  • As the progress moves past one quadrant to another, the box-shadow of the parent is changed to give the appearance of the progress bar changing its color.
  • The original CodePen uses the data-progress attribute's value directly as the content of a pseudo-element. But that value gets incremented with every iteration. Since it is also used in calculation of the skew angles, I have left it as-is and used a separate field for the countdown timer. Content of pseudo-elements cannot be set using JS and so I have added another div for the timer text.

window.onload = function() {  var progressbar = document.querySelector('div[data-progress]'),    quad1 = document.querySelector('.quad1'),    quad2 = document.querySelector('.quad2'),    quad3 = document.querySelector('.quad3'),    quad4 = document.querySelector('.quad4'),    counter = document.querySelector('.counter');
var progInc = setInterval(incrementProg, 1000); // call function every second
function incrementProg() { progress = progressbar.getAttribute('data-progress'); //get current value progress++; // increment the progress bar value by 1 with every iteration progressbar.setAttribute('data-progress', progress); //set value to attribute counter.textContent = 100 - parseInt(progress, 10); // set countdown timer's value setPie(progress); // call the paint progress bar function based on progress value if (progress == 100) { clearInterval(progInc); // clear timer when countdown is complete } }
function setPie(progress) { /* If progress is less than 25, modify skew angle the first quadrant */ if (progress <= 25) { quad1.setAttribute('style', 'transform: skew(' + progress * (-90 / 25) + 'deg)'); }
/* Between 25-50, hide 1st quadrant + modify skew angle of 2nd quadrant */ else if (progress > 25 && progress <= 50) { quad1.setAttribute('style', 'transform: skew(-90deg)'); // hides 1st completely quad2.setAttribute('style', 'transform: skewY(' + (progress - 25) * (90 / 25) + 'deg)'); progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px orange'); }
/* Between 50-75, hide first 2 quadrants + modify skew angle of 3rd quadrant */ else if (progress > 50 && progress <= 75) { quad1.setAttribute('style', 'transform: skew(-90deg)'); // hides 1st completely quad2.setAttribute('style', 'transform: skewY(90deg)'); // hides 2nd completely quad3.setAttribute('style', 'transform: skew(' + (progress - 50) * (-90 / 25) + 'deg)'); progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px yellow'); }
/* Similar to above for value between 75-100 */ else if (progress > 75 && progress <= 100) { quad1.setAttribute('style', 'transform: skew(-90deg)'); // hides 1st completely quad2.setAttribute('style', 'transform: skewY(90deg)'); // hides 2nd completely quad3.setAttribute('style', 'transform: skew(-90deg)'); // hides 3rd completely quad4.setAttribute('style', 'transform: skewY(' + (progress - 75) * (90 / 25) + 'deg)'); progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px green'); } }}
div[data-progress] {  box-sizing: border-box;  position: relative;  height: 200px;  width: 200px;  background: beige;  border-radius: 50%;  box-shadow: inset 0px 0px 0px 20px red;  transition: all 1s;  overflow: hidden;}.counter {  position: absolute;  height: 100%;  width: 100%;  top: 0%;  left: 0%;  text-align: center;  line-height: 200px;  border-radius: 50%;  background: transparent;  z-index: 4;}div > div {  position: absolute;  height: 50%;  width: 50%;  background: inherit;  border-radius: 0%;}.quad1,.quad2 {  left: 50%;  transform-origin: left bottom;}.quad3,.quad4 {  left: 0%;  transform-origin: right top;}.quad1,.quad4 {  top: 0%;}.quad2,.quad3 {  top: 50%;}.quad1,.quad3 {  transform: skew(0deg); /* invisible at -90deg */}.quad2,.quad4 {  transform: skewY(0deg); /* invisible at 90deg */}
/* Just for demo */
body { background: linear-gradient(90deg, crimson, indianred, purple);}div[data-progress] { margin: 40px auto;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
100


Related Topics



Leave a reply



Submit