Create your own circular progress bar using skiasharp

Ahmed Fouad
ITNEXT
Published in
5 min readJun 1, 2020

--

The circular progress bar is a very popular control that designers like to use in mobile apps, also the control popularity it is not a part of the official SDKs for all the mobile platforms (IOS, Android and windows).

In this article, we will implement our own cross-platform circular progress bar in less than 10 minutes :D believe me after we are done you will fail in love with mathematics.

Step 1: Install SkiaSharp NuGet package

Install the NuGet package in all your solution SkiaSharp.Views.Forms projects

https://www.nuget.org/packages/SkiaSharp.Views.Forms/

Step2: Initialize the skia view canvas

Add the canvas view in your xaml

<forms:SKCanvasView x:Name="SkCanvasView" 
HeightRequest="500"
WidthRequest="500"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand" />

Now let's create a class “ProgressDrawer” this class will be responsible for drawing and maintaining our progress bar

public class ProgressDrawer
{
private readonly SKCanvasView _canvas;
public ProgressDrawer(SKCanvasView canvas)
{
_canvas = canvas;
}
}

Basically circular progress consists of 2 parts

1- background circle that represents the full progress bar

2- an arc for the current progress value (colored in red)

So we will need to have 2 Methods in our class one for the circle and one for the arc

So our ProgressDrawer class looks like

public class ProgressDrawer
{
private readonly SKCanvasView _canvas;
public ProgressDrawer(SKCanvasView canvas)
{
_canvas = canvas;
}
public void DrawCircle(int radius, SKPoint center, float strokewidth, SKColor color)
{
}
public void DrawPercentage(Circle circle, Func<float> progress, float strokewidth, SKColor color)
{
}
}

Step 3: Draw Circle Method

Drawing a circle only require a center and radius, for now, we will assume that our center point is (0,0)

public void DrawCircle(int radius, SKPoint center, float strokewidth,SKColor color)
{
_canvas.PaintSurface += (sender, args) =>
{
args.Surface.Canvas.DrawCircle(center, radius,
new SKPaint() {
StrokeWidth = strokewidth,
Color = color,
IsStroke = true});
};
}

Skiasharp canvas can only draw in the1 handler PaintSurface Event so we add a handler that draws a circle using the draws a static circle using SKCanvas.DrawCircle Method.

For better code quality and for later usage lets create a Circle class that contains the radius and the center point properties

public class Circle
{
public SKPoint Center { get;}
public float Redius { get;}
public Circle(float redius, SKPoint center)
{
Redius = redius;
Center = center;
}

}

And now let's refactor our Draw Circle Method to use the circle class

public void DrawCircle(Circle circle, float strokewidth, SKColor color)
{
_canvas.PaintSurface += (sender, args) =>
{
args.Surface.Canvas.DrawCircle(circle.Center, circle.Redius,
new SKPaint()
{
StrokeWidth = strokewidth,
Color = color,
IsStroke = true
});
};
}

Step 4: Draw Arc Method

void DrawPercentage(Circle circle,Func<float> progress, float strokewidth, SKColor color)

As expected the draw arc method will need to know about the background circle and the progress value.

unlike the circle, arc drawing is a little bit more complex so we will need the enclosing rectangle, the start angle, and the sweep angle.

likely the enclosing rectangle is just a square of hight and width equals to the diameter of the background circle.

1- Enclosing Rectangle

so we can simply add a Rect property in the circle class

public SKRect Rect => 
new SKRect(
Center.X-Redius,
Center.Y-Redius,
Center.X+Redius,
Center.Y+Redius);

2- Start Angle

And for the start angle, as we wanna start from a vertical position (12 o'clock) it should be 90 degrees but because skiasharp angles are clockwise so we will need to flip the angle to be 270.

3- Sweep Angle

the sweep angle is the angle between the start and the end of the arc so all we need is to map the progress value which belongs to the interval [0,100] to an angle from 0 to 360.

the easiest way to do that is to use this equation

var angle = progress * 3.6f;

Now putting all together so our drawing Arc method is

public void DrawArc(Circle circle,Func<float> progress, float strokewidth, SKColor color)
{
_canvas.PaintSurface += (sender, args) =>
{
var angle = progress.Invoke() * 3.6f;
args.Surface.Canvas.DrawArc(circle.Rect, 270, angle, false,
new SKPaint() {StrokeWidth = strokewidth, Color = color, IsStroke = true});
};
}

we are using Func<float> for progress instead of taking it as a value because the progress value will keep changing and this draw method only gets called once to setup the event handler (i believe it should be renamed to SetupArcDrawing)

Setup 4: Refactor The Code

It sounds a little bit fun but actually no need to subscribe to the PrintSurface Event twice, all we need is to subscribe once and draw the circle and the arc.

The Center point position is usually relative to the canvas size and the canvas size info will be only available in the PrintSurface event so the only way to do it correctly is to keep the position calculation as a func in the circle class and to calculate position every time the print surface event fire, by the way, this will make our progress responsive for free.

Please consider making this class disposable and unsubscribe from the event handler.

Setup 5: Create the dependency property and start using the control

And now we are done, you can use the CircularProgressView as any other control in your xaml.

Step 6: Follow me

please follow me here in medium and on Twitter

if you wanna support me please share your coffee with me on ko-fi.com

And For recommendation please read The Nature Of Code by Daniel Shiffman this book will really help you to come with very simple solutions to complex coding problems regardless of your level in coding this book will improve it.

--

--

Hello, I’m Ahmed. I’m a software engineer living in Vienna, Austria. I am a fan of technology, web development, and programming. I’m also interested in xamarin