Circular Progressbar through SKIA Sharp is not working in MAUI

Satya 85 Reputation points
2023-03-27T11:27:08.9133333+00:00

I have created a custom control for circular progress bar using skia sharp in xamarin forms and it is not working in MAUI after migration. I have shared the code below, please check

    <skia:SKCanvasView
        x:Name="canvas"
        HeightRequest="150"
        HorizontalOptions="CenterAndExpand"
        PaintSurface="CanvasPaintSurface"
        VerticalOptions="CenterAndExpand"
        WidthRequest="150" />
  public partial class CircularProgressBar : ContentView
    {
        /// <summary>
        /// 
        /// </summary>
        private const int PADDING = 2;

        /// <summary>
        /// Time elapsed from previous view draw.
        /// </summary>
        private readonly Stopwatch _time = new Stopwatch();

        /// <summary>
        /// Interval between view draws.
        /// </summary>
        private readonly TimeSpan _drawInterval = TimeSpan.FromMilliseconds(30);

        /// <summary>
        /// View draw frame.
        /// </summary>
        private SKRect frame;

        /// <summary>
        /// Stroke paint configuration.
        /// </summary>
        private SKPaint paint;

        /// <summary>
        /// Background paint configuration.
        /// </summary>
        private SKPaint paintBackground;

        /// <summary>
        /// 
        /// </summary>
        private float _easing = 0;

        /// <summary>
        /// 
        /// </summary>
        private float _rotate = 0;

        /// <summary>
        /// 
        /// </summary>
        private float _currentProgress = 0.0f;

        /// <summary>
        /// 
        /// </summary>
        private float _value;

        /// <summary>
        /// Bindable property of <see cref="Progress"/>.
        /// </summary>
        public static readonly BindableProperty ProgressProperty = BindableProperty.Create(
            nameof(Progress),
            typeof(double),
            typeof(CircularProgressBar),
            0d,
            propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
            {
                var circularProgressBar = ((CircularProgressBar)bindable);
                circularProgressBar._easing = 0;
                circularProgressBar.canvas.InvalidateSurface();
            });

        /// <summary>
        /// Bindable property of <see cref="Color"/>.
        /// </summary>
        public static readonly BindableProperty ColorProperty = BindableProperty.Create(
            nameof(Color),
            typeof(Color),
            typeof(CircularProgressBar),
            KnownColor.Accent,
            propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
            {
                var circularProgressBar = ((CircularProgressBar)bindable);
                circularProgressBar.UpdatePaint();
                circularProgressBar.canvas.InvalidateSurface();
            });

        /// <summary>
        /// Bindable property of <see cref="BackgroundStokeColor"/>.
        /// </summary>
        public static readonly BindableProperty BackgroundStokeColorProperty = BindableProperty.Create(
            nameof(BackgroundStokeColor),
            typeof(Color),
            typeof(CircularProgressBar),
            KnownColor.Accent,
            propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
            {
                var circularProgressBar = ((CircularProgressBar)bindable);
                circularProgressBar.UpdatePaint();
                circularProgressBar.canvas.InvalidateSurface();
            });


        /// <summary>
        /// Bindable property of <see cref="Stroke"/>.
        /// </summary>
        public static readonly BindableProperty StrokeProperty = BindableProperty.Create(
            nameof(Stroke),
            typeof(double),
            typeof(CircularProgressBar),
            9d,
            propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
            {
                var circularProgressBar = ((CircularProgressBar)bindable);
                circularProgressBar.UpdatePaint();
                circularProgressBar.canvas.InvalidateSurface();
            });

        /// <summary>
        /// Bindable property of <see cref="Spin"/>.
        /// </summary>
        public static readonly BindableProperty SpinProperty = BindableProperty.Create(
            nameof(Spin),
            typeof(bool),
            typeof(CircularProgressBar),
            false,
            propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
            {
                var circularProgressBar = ((CircularProgressBar)bindable);
                circularProgressBar.canvas.InvalidateSurface();
            });

        /// <summary>
        /// Bindable property of <see cref="Easing"/>.
        /// </summary>
        public static readonly BindableProperty EasingProperty = BindableProperty.Create(
            nameof(Easing),
            typeof(bool),
            typeof(CircularProgressBar),
            false,
            propertyChanged: (BindableObject bindable, object oldValue, object newValue) =>
            {
                var circularProgressBar = ((CircularProgressBar)bindable);
                circularProgressBar.canvas.InvalidateSurface();
            });

        /// <summary>
        /// Progress bar value (in percentages).
        /// </summary>
        public double Progress
        {
            get => (double)GetValue(ProgressProperty);
            set
            {
                _currentProgress = _value;
                SetValue(ProgressProperty, Math.Max(0, Math.Min(100, value)));
            }
        }

        /// <summary>
        /// Progress bar stroke color.
        /// </summary>
        public Color Color
        {
            get => (Color)GetValue(ColorProperty);
            set => SetValue(ColorProperty, value);
        }

        /// <summary>
        /// Progress bar stroke color.
        /// </summary>
        public Color BackgroundStokeColor
        {
            get => (Color)GetValue(BackgroundStokeColorProperty);
            set => SetValue(BackgroundStokeColorProperty, value);
        }


        /// <summary>
        /// Progress bar stroke thickness.
        /// </summary>
        public double Stroke
        {
            get => (double)GetValue(StrokeProperty);
            set => SetValue(StrokeProperty, value);
        }

        /// <summary>
        /// Should spin progress bar.
        /// </summary>
        public bool Spin
        {
            get => (bool)GetValue(SpinProperty);
            set => SetValue(SpinProperty, value);
        }

        /// <summary>
        /// Should change progress bar <see cref="Value"/> with easing.
        /// </summary>
        public bool Easing
        {
            get => (bool)GetValue(EasingProperty);
            set => SetValue(EasingProperty, value);
        }

        /// <summary>
        /// Create new instance of <see cref="CircularProgressBar"/>.
        /// </summary>
        public CircularProgressBar()
        {
            UpdatePaint();
            InitializeComponent();
        }

        /// <inheritdoc/>
        protected override void InvalidateLayout()
        {
            base.InvalidateLayout();
            canvas.InvalidateSurface();
        }

        /// <summary>
        /// Update <see cref="paint"/> and <see cref="paintBackground"/>.
        /// </summary>
        private void UpdatePaint()
        {
            paint = new SKPaint
            {
                Style = SKPaintStyle.Stroke,
              //  Color = Color.ToSKColor(),
                StrokeCap = SKStrokeCap.Round,
                StrokeJoin = SKStrokeJoin.Round,
                StrokeWidth = (float)ConvertToPixels(Stroke),
                IsAntialias = true,
            };

            paintBackground = paint.Clone();
            paintBackground.StrokeWidth *= 0.25f;

           // paintBackground.Color = BackgroundStokeColor.ToSKColor();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void CanvasPaintSurface(object sender, SKPaintSurfaceEventArgs args)
        {
            _time.Stop();

            // Elapsed milliseconds from previous view draw
            float elapsed = (float)Math.Max(0.1, Math.Min(10, _time.ElapsedMilliseconds / _drawInterval.TotalMilliseconds));

            _time.Reset();
            _time.Start();

            if (Easing || Spin)
            {
                _easing += 0.05f * elapsed;
                _easing = Math.Min(1, _easing);
            }
            else
            {
                _easing = 1;
            }

            if (Spin)
            {
                _rotate += 8f * elapsed;

                if (_rotate > 360)
                    _rotate = _rotate - 360;
            }
            else
            {
                _rotate = 0;
            }

            if (Progress != _value
                || (Spin && Progress > 0 && Progress < 100)
                || (Easing && _easing > 0 && _easing < 1))
            {
                Dispatcher.StartTimer(_drawInterval, () =>
                {
                    canvas.InvalidateSurface();
                    return false;
                });
            }

            args.Surface.Canvas.Clear();

            frame.Size = new SKSize(
                args.Info.Width - paint.StrokeWidth - PADDING,
                args.Info.Height - paint.StrokeWidth - PADDING);

            frame.Location = new SKPoint(
                paint.StrokeWidth / 2,
                paint.StrokeWidth / 2);

            float delta = ((float)Progress - _currentProgress) * (float)Microsoft.Maui.Easing.CubicInOut.Ease(_easing);

            _value = _currentProgress + delta;

            float startAngle = _rotate + 270f;
            float sweepAngle = _value / 100f * 360f;

            SKPath path = new SKPath();
            path.AddArc(frame, startAngle, sweepAngle);

            SKPath pathBackground = new SKPath();
            pathBackground.AddArc(frame, 0, 360);

            args.Surface.Canvas.DrawPath(pathBackground, paintBackground);
            args.Surface.Canvas.DrawPath(path, paint);
        }


        /// <summary>
        /// Converts Xamarin units into pixels.
        /// https://stackoverflow.com/a/63615455/6499748
        /// </summary>
        /// <param name="value">Xamarin units.</param>
        /// <returns>Value in pixel</returns>
        private static double ConvertToPixels(double value)
            => (DeviceDisplay.MainDisplayInfo.Density / 2d) * value;
    }
.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
2,837 questions
{count} votes