From 8f1eba7fbeafcc65a23d61ee097ff0ef0764ae65 Mon Sep 17 00:00:00 2001 From: logikonline Date: Fri, 16 Jan 2026 05:03:49 +0000 Subject: [PATCH] Activity completed --- Handlers/ActivityIndicatorHandler.cs | 25 ++++++- Views/SkiaActivityIndicator.cs | 107 ++++++++++++++++++++------- 2 files changed, 103 insertions(+), 29 deletions(-) diff --git a/Handlers/ActivityIndicatorHandler.cs b/Handlers/ActivityIndicatorHandler.cs index a61c96c..b8bcd98 100644 --- a/Handlers/ActivityIndicatorHandler.cs +++ b/Handlers/ActivityIndicatorHandler.cs @@ -3,7 +3,7 @@ using Microsoft.Maui.Handlers; using Microsoft.Maui.Graphics; -using SkiaSharp; +using Microsoft.Maui.Platform; namespace Microsoft.Maui.Platform.Linux.Handlers; @@ -18,6 +18,7 @@ public partial class ActivityIndicatorHandler : ViewHandler CommandMapper = new(ViewHandler.ViewCommandMapper) @@ -38,6 +39,19 @@ public partial class ActivityIndicatorHandler : ViewHandler -/// Skia-rendered activity indicator (spinner) control with full XAML styling support. +/// Skia-rendered activity indicator (spinner) control with full MAUI compliance. +/// Implements IActivityIndicator interface requirements: +/// - IsRunning property to start/stop animation +/// - Color property for the indicator color /// public class SkiaActivityIndicator : SkiaView { + #region SKColor Helper + + private static SKColor ToSKColor(Color? color) + { + if (color == null) return SKColors.Transparent; + return new SKColor( + (byte)(color.Red * 255), + (byte)(color.Green * 255), + (byte)(color.Blue * 255), + (byte)(color.Alpha * 255)); + } + + #endregion + #region BindableProperties /// @@ -31,9 +50,9 @@ public class SkiaActivityIndicator : SkiaView public static readonly BindableProperty ColorProperty = BindableProperty.Create( nameof(Color), - typeof(SKColor), + typeof(Color), typeof(SkiaActivityIndicator), - new SKColor(0x21, 0x96, 0xF3), + Color.FromRgb(0x21, 0x96, 0xF3), // Material Blue BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).Invalidate()); @@ -43,9 +62,9 @@ public class SkiaActivityIndicator : SkiaView public static readonly BindableProperty DisabledColorProperty = BindableProperty.Create( nameof(DisabledColor), - typeof(SKColor), + typeof(Color), typeof(SkiaActivityIndicator), - new SKColor(0xBD, 0xBD, 0xBD), + Color.FromRgb(0xBD, 0xBD, 0xBD), BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).Invalidate()); @@ -55,9 +74,9 @@ public class SkiaActivityIndicator : SkiaView public static readonly BindableProperty SizeProperty = BindableProperty.Create( nameof(Size), - typeof(float), + typeof(double), typeof(SkiaActivityIndicator), - 32f, + 32.0, BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).InvalidateMeasure()); @@ -67,9 +86,9 @@ public class SkiaActivityIndicator : SkiaView public static readonly BindableProperty StrokeWidthProperty = BindableProperty.Create( nameof(StrokeWidth), - typeof(float), + typeof(double), typeof(SkiaActivityIndicator), - 3f, + 3.0, BindingMode.TwoWay, propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).InvalidateMeasure()); @@ -79,9 +98,9 @@ public class SkiaActivityIndicator : SkiaView public static readonly BindableProperty RotationSpeedProperty = BindableProperty.Create( nameof(RotationSpeed), - typeof(float), + typeof(double), typeof(SkiaActivityIndicator), - 360f, + 360.0, BindingMode.TwoWay); /// @@ -112,45 +131,45 @@ public class SkiaActivityIndicator : SkiaView /// /// Gets or sets the indicator color. /// - public SKColor Color + public Color Color { - get => (SKColor)GetValue(ColorProperty); + get => (Color)GetValue(ColorProperty); set => SetValue(ColorProperty, value); } /// /// Gets or sets the disabled color. /// - public SKColor DisabledColor + public Color DisabledColor { - get => (SKColor)GetValue(DisabledColorProperty); + get => (Color)GetValue(DisabledColorProperty); set => SetValue(DisabledColorProperty, value); } /// /// Gets or sets the indicator size. /// - public float Size + public double Size { - get => (float)GetValue(SizeProperty); + get => (double)GetValue(SizeProperty); set => SetValue(SizeProperty, value); } /// /// Gets or sets the stroke width. /// - public float StrokeWidth + public double StrokeWidth { - get => (float)GetValue(StrokeWidthProperty); + get => (double)GetValue(StrokeWidthProperty); set => SetValue(StrokeWidthProperty, value); } /// /// Gets or sets the rotation speed in degrees per second. /// - public float RotationSpeed + public double RotationSpeed { - get => (float)GetValue(RotationSpeedProperty); + get => (double)GetValue(RotationSpeedProperty); set => SetValue(RotationSpeedProperty, value); } @@ -165,9 +184,15 @@ public class SkiaActivityIndicator : SkiaView #endregion + #region Private Fields + private float _rotationAngle; private DateTime _lastUpdateTime = DateTime.UtcNow; + #endregion + + #region Event Handlers + private void OnIsRunningChanged() { if (IsRunning) @@ -177,6 +202,10 @@ public class SkiaActivityIndicator : SkiaView Invalidate(); } + #endregion + + #region Rendering + protected override void OnDraw(SKCanvas canvas, SKRect bounds) { if (!IsRunning && !IsEnabled) @@ -184,9 +213,13 @@ public class SkiaActivityIndicator : SkiaView return; } + var size = (float)Size; + var strokeWidth = (float)StrokeWidth; + var rotationSpeed = (float)RotationSpeed; + var centerX = bounds.MidX; var centerY = bounds.MidY; - var radius = Math.Min(Size / 2, Math.Min(bounds.Width, bounds.Height) / 2) - StrokeWidth; + var radius = Math.Min(size / 2, Math.Min(bounds.Width, bounds.Height) / 2) - strokeWidth; // Update rotation if (IsRunning) @@ -194,27 +227,27 @@ public class SkiaActivityIndicator : SkiaView var now = DateTime.UtcNow; var elapsed = (now - _lastUpdateTime).TotalSeconds; _lastUpdateTime = now; - _rotationAngle = (_rotationAngle + (float)(RotationSpeed * elapsed)) % 360; + _rotationAngle = (_rotationAngle + (float)(rotationSpeed * elapsed)) % 360; } canvas.Save(); canvas.Translate(centerX, centerY); canvas.RotateDegrees(_rotationAngle); - var color = IsEnabled ? Color : DisabledColor; + var colorSK = ToSKColor(IsEnabled ? Color : DisabledColor); // Draw arcs with varying opacity for (int i = 0; i < ArcCount; i++) { var alpha = (byte)(255 * (1 - (float)i / ArcCount)); - var arcColor = color.WithAlpha(alpha); + var arcColor = colorSK.WithAlpha(alpha); using var paint = new SKPaint { Color = arcColor, IsAntialias = true, Style = SKPaintStyle.Stroke, - StrokeWidth = StrokeWidth, + StrokeWidth = strokeWidth, StrokeCap = SKStrokeCap.Round }; @@ -239,8 +272,28 @@ public class SkiaActivityIndicator : SkiaView } } + #endregion + + #region Lifecycle + + protected override void OnEnabledChanged() + { + base.OnEnabledChanged(); + SkiaVisualStateManager.GoToState(this, IsEnabled + ? SkiaVisualStateManager.CommonStates.Normal + : SkiaVisualStateManager.CommonStates.Disabled); + } + + #endregion + + #region Layout + protected override SKSize MeasureOverride(SKSize availableSize) { - return new SKSize(Size + StrokeWidth * 2, Size + StrokeWidth * 2); + var size = (float)Size; + var strokeWidth = (float)StrokeWidth; + return new SKSize(size + strokeWidth * 2, size + strokeWidth * 2); } + + #endregion }