Create your own circular progress bar using skiasharp
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.