Flutter Elliptical Progress Bar: Code & Calculations
Learn how to create a custom elliptical progress bar in Flutter with step-by-step explanations of the math, logic, and drawing calculations.
Published On 2023-10-15

By Swasti Jain
INTRODUCTION
Progress bars are everywhere — from showing app loading states to tracking goals. But while circular or linear progress bars are common in Flutter, an elliptical progress bar isn’t available out of the box.
In this post, we’ll explore how to create one from scratch. We’ll break down the ellipse into smaller parts, understand the math behind it, and walk through the logic step by step until we have a working elliptical progress bar in Flutter.
1. DIVIDING THE ELLIPSE INTO SECTIONS
When creating an elliptical progress bar, it helps to simplify the shape into smaller parts. Instead of dealing with the full ellipse at once, we break it down into segments. The progress is then shown by filling these segments step by step to represent the current value.
In this approach, the ellipse’s path is divided into six sections:
- First line AB
- First Arc with center X
- Second line DE
- Third line EF
- Second Arc with center Y
- Fourth line HA

sectioned-ellipse
2. CALCULATING THE START AND END POINTS

screen coordinates
Important before you proceed - For the canvas paint, all coordinate
points are calculated keeping the origin on the leftmost screen
For example, a point at a distance of X from the left edge of the
screen will have an x-ordinate as X. Similarly, a point at a distance
of Y from the top edge of the screen will have a y-ordinate as Y.
Let the width of the ellipse container be W and the height be H
Let the thickness of the ellipse be t.
We will be drawing the white path as the progress path. It is the center of the progress loop we need. Giving stroke width t/2 to the purple path will make it fill the ellipse completely.

sectioned ellipse with maths
1. LINE AB
Then coordinates of the start point A of line AB = (W/2, t/2) ………..(1)
2. FIRST ARC BCD
Let L be the endpoint on the edge of the first line of the ellipse,
L is at the top edge of the screen, hence Ly = 0
Let the center of this arc be X
Radius r(XB) = XL - BL = H/2 - t/2
x-ordinate of X = NM - XM = NM - XC - CM
= W - r - t/2
Y-ordinate of X = H/2
Therefore (Xx, Xy) = (W - r - t/2, H/2) …………………(2)
3. Second line DE
Endpoint E = (A, H - t/2)
E = ( W/2, H - t/2 ) …………………(3)
4. For third line EF,
X-ordinate of F and Y are the same
i.e. Fx = Yx
Yx = NG + GY
therefore
Endpoint Fx = NG + GY = r + t/2
Fy = H - t/2
F = (r + t/2, H - t/2) …………………(4)
5. Second arc FGH
Center Y = ( r + t/2, H/2 ) …………………(5)
6. Fourth line HA
To endpoint A = (W/2, t/2), as calculated in (1)
The ellipse is divided into 6 segments as elaborated above; the progress value needs to be divided into 6 parts to represent an accurate completion fraction.
Therefore
progress = progressValue / 6
Let progress for each segment be calculated in an array called sections
1int x = (progress / 100).floor();
2var sections = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
3
4for (int i = 0; i < x; i++) {
5 sections[i] = 1;
6}
7
8if (x <= 5) {
9 sections[x] = (progress % 100) / 100;
10}
VISUALISATION OF THE ABOVE CALCULATION
For a progressValue of 60,
progress = progressValue * 6 = 360
x represents the segments that will be filled fully
x = Floor(360/100) = 3
sections = [ 1, 1, 1, 0.6, 0, 0 ]
This represents that all the parts need to be filled in the calculated order as follows:

numbered sections of ellipse
First line AB : sections[0] = 1 means 100% (full)
First Arc BCD : sections[1] = 1 means 100% (full)
Second line DE : sections[2] = 1 means 100% (full)
Third line EF : sections[3] = 0.6 means 60% (partial)
Second Arc FGH : sections[4] = 0 means 0% (none)
Fourth line HA : sections[5] = 0 means 0% (none)

sub sections of ellipse
Calculating the lengths of the lines
len1 = Bx - Ax;
len2 = Dx - Ex
= Bx - Ex ( since Bx = Dx as can be seen from fig)
len3 = Ex - Fx ;
len4 = Ax - Hx;
3. PAINTING
Now according to the sections we have calculated and the x value, we paint the canvas. We will paint only the progress amount, which will be calculated as -
Length of the part \* section value of the part
Visualization -> For a segment having a section value of 0.6, only 60% of the total length of that segment needs to be filled hence we will paint to only 60% of the total length
CASE 1: x = 0 -> only first segment is to be painted

case 1
1if (x == 0) {
2 final path = Path()
3 ..moveTo(startPointX, thickness! / 2)
4 ..lineTo(
5 startPointX + len1 * sections[0],
6 thickness! / 2,
7 ); // line1
8}
1canvas.drawPath(path, paint);
CASE 2: x = 1
first segment + first arc is to be painted
The full arc is made by painting an arc of 180 degrees. ( -90 to 90 in canvas paint )
Hence progress to be painted for the arc can be calculated as follows -
Section value of the arc * 180
Note: math.pi / 180 is multiplied for conversion from radian into
degrees

case 2
1else if (x == 1) {
2 final path = Path()
3 ..moveTo(startPointX, thickness! / 2)
4 ..lineTo(firstArcStartX, thickness! / 2) // line1
5 ..arcTo(
6 Rect.fromCircle(center: firstArcCenter, radius: r),
7 startAngleRad,
8 sections[1] * 180 * (math.pi / 180.0),
9 true,
10 );
11
12 canvas.drawPath(path, paint);
13}
CASE 3: x = 2
first segment + first arc + second line is to be painted

case 3
1else if (x == 2) {
2 final path = Path()
3 ..moveTo(startPointX, thickness! / 2)
4 ..lineTo(firstArcStartX, thickness! / 2) // line1
5 ..arcTo(
6 Rect.fromCircle(center: firstArcCenter, radius: r),
7 startAngleRad,
8 sections[1] * 180 * (math.pi / 180.0),
9 true,
10 )
11 ..lineTo(
12 firstArcStartX - len2 * sections[2],
13 size.height - thickness! / 2,
14 ); // line2
15
16 canvas.drawPath(path, paint);
17}
CASE 4: x = 3
first line + first arc + second line + third line

case 4
1else if (x == 3) {
2 final path = Path()
3 ..moveTo(startPointX, thickness! / 2)
4 ..lineTo(firstArcStartX, thickness! / 2) // line1
5 ..arcTo(
6 Rect.fromCircle(center: firstArcCenter, radius: r),
7 startAngleRad,
8 sections[1] * 180 * (math.pi / 180.0),
9 true,
10 )
11 ..lineTo(
12 firstArcStartX - len2 * sections[2],
13 size.height - thickness! / 2,
14 ) // line2
15 ..lineTo(
16 secondLineEndX - len3 * sections[3],
17 size.height - thickness! / 2,
18 ); // line3
19
20 canvas.drawPath(path, paint);
21}
CASE 5: x = 4
first line + first arc + second line + third line + second arc

case 5
1else if (x == 4) {
2 final path = Path()
3 ..moveTo(startPointX, thickness! / 2)
4 ..lineTo(firstArcStartX, thickness! / 2) // line1
5 ..arcTo(
6 Rect.fromCircle(center: firstArcCenter, radius: r),
7 startAngleRad,
8 sections[1] * 180 * (math.pi / 180.0),
9 true,
10 )
11 ..lineTo(
12 firstArcStartX - len2 * sections[2],
13 size.height - thickness! / 2,
14 ) // line2
15 ..lineTo(
16 secondLineEndX - len3 * sections[3],
17 size.height - thickness! / 2,
18 ) // line3
19 ..arcTo(
20 Rect.fromCircle(center: secondArcCenter, radius: r),
21 startAngleRad + piInRadian,
22 sections[4] * 180 * (math.pi / 180.0),
23 true,
24 );
25
26 canvas.drawPath(path, paint);
27}
CASE 6: x = 5
first line + first arc + second line + third line + second arc + fourth line

case 6
1else {
2 final path = Path()
3 ..moveTo(startPointX, thickness! / 2)
4 ..lineTo(firstArcStartX, thickness! / 2) // line1
5 ..arcTo(
6 Rect.fromCircle(center: firstArcCenter, radius: r),
7 startAngleRad,
8 sections[1] * 180 * (math.pi / 180.0),
9 true,
10 )
11 ..lineTo(
12 firstArcStartX - len2 * sections[2],
13 size.height - thickness! / 2,
14 ) // line2
15 ..lineTo(
16 secondLineEndX - len3 * sections[3],
17 size.height - thickness! / 2,
18 ) // line3
19 ..arcTo(
20 Rect.fromCircle(center: secondArcCenter, radius: r),
21 startAngleRad + piInRadian,
22 sections[4] * 180 * (math.pi / 180.0),
23 true,
24 )
25 ..lineTo(
26 thirdLineEndX + len4 * sections[5],
27 thickness! / 2,
28 ); // line4
29
30 canvas.drawPath(path, paint);
31}
Building an elliptical progress bar in Flutter becomes much simpler once you break the ellipse into smaller segments and map progress across them. By combining a bit of math with Flutter’s Canvas and Path, you can create a fully custom progress indicator.
This method not only helps visualize progress in a unique way but also gives you the foundation to design other creative, custom UI elements in Flutter.
Thanks for reading!