1

I need to create a progress bar that looks like this.

enter image description here

What I did for now is:

public void Draw(ICanvas canvas, RectF dirtyRect)
{
    float effectiveSize = Size - Thickness;
    float x = Thickness / 2;
    float y = Thickness / 2;

    if (Progress < 0)
    {
        Progress = 0;
    }
    else if (Progress > 100)
    {
        Progress = 100;
    }

    if (Progress < 100)
    {
        float angle = GetAngle(Progress);

        canvas.StrokeColor = ProgressLeftColor;
        canvas.StrokeSize = Thickness;
        canvas.DrawEllipse(x, y, effectiveSize, effectiveSize);

        // Draw arc
        canvas.StrokeColor = ProgressColor;
        canvas.StrokeSize = Thickness;
        canvas.DrawArc(x, y, effectiveSize, effectiveSize, 90, angle, true, false);
    }
    else
    {
        // Draw circle
        canvas.StrokeColor = ProgressColor;
        canvas.StrokeSize = Thickness;
        canvas.DrawEllipse(x, y, effectiveSize, effectiveSize);
    }

    float fontSize = effectiveSize / 2.86f;
    canvas.FontSize = fontSize;
    canvas.FontColor = TextColor;

    float verticalPosition = ((Size / 2) - (fontSize / 2)) * 1.15f;
    canvas.DrawString($"{Progress}%", x, verticalPosition, effectiveSize, effectiveSize / 4, HorizontalAlignment.Center, VerticalAlignment.Center);
}

This draws a nice circle with an arc. I am struggling with the inner pie part (light gray part). I need that part to be filled with some color.

One of my ideas was to create two lines then connect them with an arc, and fill the object. Something like this.

canvas.FillColor = Colors.Teal;

var center = new Point(effectiveSize / 2, effectiveSize / 2);
var startPoint = new Point(x, y);
var endPoint = new Point();
canvas.DrawLine(center, startPoint);
canvas.DrawLine(center, endPoint);
canvas.DrawArc((float)startPoint.X, (float)startPoint.Y,float)endPoint.X, (float)endPoint.Y, 0, angle, true, false);

How can I calculate the endpoint. Or is there any better solution.

thnx

enter image description here

Peter Wessberg
  • 879
  • 6
  • 13
Wasyster
  • 2,279
  • 4
  • 26
  • 58
  • It's probably simpler if you build a Path geometry, both for the outer arc and the inner *cone*. The former can also give you the last point of the path. It's something like [this](https://stackoverflow.com/a/74622050/7444103) (not MAUI, though, but the concept is ~the same). Or you can calculate the rotation yourself; see the `RotatePoint()` method here: [Rotate Point around pivot Point repeatedly](https://stackoverflow.com/a/50478311/7444103) – Jimi Jul 29 '23 at 11:56

2 Answers2

3

You may try using SkiaSharp to draw this pie. I share a solution here. For more info, you may refer to SkiaSharp Curves and Paths. It also works for maui.

Use a slider to control the angle:

<VerticalStackLayout
    Spacing="25"
    Padding="30,0"
    VerticalOptions="Center">

    
    <Slider Maximum="360" ValueChanged="Slider_ValueChanged" />

    <skia:SKCanvasView x:Name="mycanvas" WidthRequest="400" HeightRequest="400" PaintSurface="SKCanvasView_PaintSurface"/>


</VerticalStackLayout>

In code behind:

void Slider_ValueChanged(System.Object sender, Microsoft.Maui.Controls.ValueChangedEventArgs e)
{
    if (e.NewValue >= 360)
    {
        // the value cannnot be equal to 360
        SweepAngle = (float)359.99;
    }
    else
    {
        SweepAngle = (float)e.NewValue;
    }
    mycanvas.InvalidateSurface();
}

void SKCanvasView_PaintSurface(System.Object sender, SkiaSharp.Views.Maui.SKPaintSurfaceEventArgs e)
{
    SKImageInfo info = e.Info;
    SKSurface surface = e.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();


    // draw the default background circle ring
    SKPaint cPaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.LightGray,
        StrokeWidth = 50
    };
    SKPoint center = new SKPoint(info.Width / 2, info.Height / 2);
    canvas.DrawCircle(center, 200, cPaint);

    cPaint.Color = SKColors.AliceBlue;
    cPaint.Style = SKPaintStyle.Fill;
    canvas.DrawCircle(center, 200, cPaint);


    //draw the progress bar outer ring
    SKRect rect = new SKRect(center.X-200, center.Y-200, center.X + 200, center.Y + 200);

    SKPaint aPaint = new SKPaint()
    {

        StrokeWidth = 50,
        Color = SKColors.Green,
        IsStroke = true
    };

    canvas.DrawArc(rect, -90, SweepAngle, false,aPaint);
    
    // draw the inner circle of the progress bar
    using (SKPath path = new SKPath())
    using (SKPaint fillPaint = new SKPaint())
    using (SKPaint outlinePaint = new SKPaint())
    {
        SKRect rect1 = new SKRect(center.X - 200, center.Y - 200, center.X + 200, center.Y + 200);

        path.MoveTo(e.Info.Width / 2, e.Info.Height / 2);
        path.ArcTo(rect1, -90, SweepAngle, false);

        fillPaint.Style = SKPaintStyle.Fill;
        fillPaint.Color = SKColors.LightGreen;

        canvas.DrawPath(path, fillPaint);
    }
    SKPaint textPaint = new SKPaint
    {
        Color = SKColors.LightSeaGreen,
        StrokeWidth = 1,
    };

    // draw the text
    string text = (Math.Round(SweepAngle/360*100,0)).ToString() + "%";
    float textWidth = textPaint.MeasureText(text);
    textPaint.TextSize = 0.2f * info.Width * textPaint.TextSize / textWidth;

    SKRect textBounds = new SKRect();
    textPaint.MeasureText(text, ref textBounds);

    float xText = info.Width / 2 - textBounds.MidX;
    float yText = info.Height / 2 - textBounds.MidY;

    canvas.DrawText(text, xText, yText, textPaint);
}

enter image description here

Hope it helps!

Liqun Shen-MSFT
  • 3,490
  • 2
  • 3
  • 11
2

You already have a good answer using Skia but if you want to use Microsoft.Maui.Graphics here is an alternative. No text because even if that can be added in the drawable it is better in a ContentView that encapsulate all this, if you are doing a control, that is. From there you can add BindableProperty and all that kind of mechanics.

public class ProgressbarDrawable : IDrawable
{
    public double Progress { get; set; } = 100;

    public void Draw(ICanvas canvas, RectF dirtyRect)
    {
        canvas.SaveState();
        var startAngle = 90;
        var progressAngle = Progress / 100;
        var endAngle = startAngle - (int)Math.Round(progressAngle * 360, MidpointRounding.AwayFromZero);
        var isFullCircle = Progress == 100;

        var centerX = dirtyRect.Width / 2;
        var centerY = dirtyRect.Height / 2;
        var radius = dirtyRect.Width / 2;

        canvas.StrokeColor = new Color(243, 242, 238);
        canvas.StrokeSize = 10;
        canvas.DrawCircle(centerX, centerY, radius);

        if (isFullCircle)
        {
            canvas.FillColor = new Color(235, 243, 231);
            canvas.FillCircle(centerX, centerY, radius);
            canvas.StrokeColor = new Color(132, 189, 137);
            canvas.StrokeSize = 10;
            canvas.DrawCircle(centerX, centerY, radius);
        }
        else
        {

            var path = new PathF();
            path.MoveTo(centerX, centerY);
            path.AddArc(centerX - radius, centerY - radius, radius * 2, radius * 2, startAngle, endAngle, true);
            path.LineTo(centerX, centerY);
            canvas.FillColor = new Color(235, 243, 231);
            canvas.FillPath(path);

            canvas.StrokeColor = new Color(132, 189, 137);
            canvas.StrokeSize = 10;
            canvas.DrawArc(centerX - radius, centerY - radius, radius * 2, radius * 2, startAngle, endAngle, true, false);
        }

        canvas.RestoreState();
    }
}

Implementation

<ContentPage.Resources>
    <drawables:ProgressbarDrawable x:Key="ProgressbarDrawable" />
</ContentPage.Resources>

<GraphicsView
    x:Name="ProgressbarDrawable"
    Drawable="{StaticResource ProgressbarDrawable}"
    HeightRequest="200"
    WidthRequest="200" />
Peter Wessberg
  • 879
  • 6
  • 13