Activity completed

This commit is contained in:
2026-01-16 05:03:49 +00:00
parent 436c9d60cb
commit 8f1eba7fbe
2 changed files with 103 additions and 29 deletions

View File

@@ -3,7 +3,7 @@
using Microsoft.Maui.Handlers; using Microsoft.Maui.Handlers;
using Microsoft.Maui.Graphics; using Microsoft.Maui.Graphics;
using SkiaSharp; using Microsoft.Maui.Platform;
namespace Microsoft.Maui.Platform.Linux.Handlers; namespace Microsoft.Maui.Platform.Linux.Handlers;
@@ -18,6 +18,7 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
[nameof(IActivityIndicator.IsRunning)] = MapIsRunning, [nameof(IActivityIndicator.IsRunning)] = MapIsRunning,
[nameof(IActivityIndicator.Color)] = MapColor, [nameof(IActivityIndicator.Color)] = MapColor,
[nameof(IView.Background)] = MapBackground, [nameof(IView.Background)] = MapBackground,
[nameof(IView.IsEnabled)] = MapIsEnabled,
}; };
public static CommandMapper<IActivityIndicator, ActivityIndicatorHandler> CommandMapper = new(ViewHandler.ViewCommandMapper) public static CommandMapper<IActivityIndicator, ActivityIndicatorHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
@@ -38,6 +39,19 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
return new SkiaActivityIndicator(); return new SkiaActivityIndicator();
} }
protected override void ConnectHandler(SkiaActivityIndicator platformView)
{
base.ConnectHandler(platformView);
// Sync properties
if (VirtualView != null)
{
MapIsRunning(this, VirtualView);
MapColor(this, VirtualView);
MapIsEnabled(this, VirtualView);
}
}
public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator) public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
{ {
if (handler.PlatformView is null) return; if (handler.PlatformView is null) return;
@@ -49,7 +63,7 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
if (handler.PlatformView is null) return; if (handler.PlatformView is null) return;
if (activityIndicator.Color is not null) if (activityIndicator.Color is not null)
handler.PlatformView.Color = activityIndicator.Color.ToSKColor(); handler.PlatformView.Color = activityIndicator.Color;
} }
public static void MapBackground(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator) public static void MapBackground(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
@@ -61,4 +75,11 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor(); handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
} }
} }
public static void MapIsEnabled(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
{
if (handler.PlatformView is null) return;
handler.PlatformView.IsEnabled = activityIndicator.IsEnabled;
handler.PlatformView.Invalidate();
}
} }

View File

@@ -1,16 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using System;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using SkiaSharp; using SkiaSharp;
namespace Microsoft.Maui.Platform; namespace Microsoft.Maui.Platform;
/// <summary> /// <summary>
/// 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
/// </summary> /// </summary>
public class SkiaActivityIndicator : SkiaView 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 #region BindableProperties
/// <summary> /// <summary>
@@ -31,9 +50,9 @@ public class SkiaActivityIndicator : SkiaView
public static readonly BindableProperty ColorProperty = public static readonly BindableProperty ColorProperty =
BindableProperty.Create( BindableProperty.Create(
nameof(Color), nameof(Color),
typeof(SKColor), typeof(Color),
typeof(SkiaActivityIndicator), typeof(SkiaActivityIndicator),
new SKColor(0x21, 0x96, 0xF3), Color.FromRgb(0x21, 0x96, 0xF3), // Material Blue
BindingMode.TwoWay, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).Invalidate());
@@ -43,9 +62,9 @@ public class SkiaActivityIndicator : SkiaView
public static readonly BindableProperty DisabledColorProperty = public static readonly BindableProperty DisabledColorProperty =
BindableProperty.Create( BindableProperty.Create(
nameof(DisabledColor), nameof(DisabledColor),
typeof(SKColor), typeof(Color),
typeof(SkiaActivityIndicator), typeof(SkiaActivityIndicator),
new SKColor(0xBD, 0xBD, 0xBD), Color.FromRgb(0xBD, 0xBD, 0xBD),
BindingMode.TwoWay, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).Invalidate());
@@ -55,9 +74,9 @@ public class SkiaActivityIndicator : SkiaView
public static readonly BindableProperty SizeProperty = public static readonly BindableProperty SizeProperty =
BindableProperty.Create( BindableProperty.Create(
nameof(Size), nameof(Size),
typeof(float), typeof(double),
typeof(SkiaActivityIndicator), typeof(SkiaActivityIndicator),
32f, 32.0,
BindingMode.TwoWay, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).InvalidateMeasure()); propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).InvalidateMeasure());
@@ -67,9 +86,9 @@ public class SkiaActivityIndicator : SkiaView
public static readonly BindableProperty StrokeWidthProperty = public static readonly BindableProperty StrokeWidthProperty =
BindableProperty.Create( BindableProperty.Create(
nameof(StrokeWidth), nameof(StrokeWidth),
typeof(float), typeof(double),
typeof(SkiaActivityIndicator), typeof(SkiaActivityIndicator),
3f, 3.0,
BindingMode.TwoWay, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).InvalidateMeasure()); propertyChanged: (b, o, n) => ((SkiaActivityIndicator)b).InvalidateMeasure());
@@ -79,9 +98,9 @@ public class SkiaActivityIndicator : SkiaView
public static readonly BindableProperty RotationSpeedProperty = public static readonly BindableProperty RotationSpeedProperty =
BindableProperty.Create( BindableProperty.Create(
nameof(RotationSpeed), nameof(RotationSpeed),
typeof(float), typeof(double),
typeof(SkiaActivityIndicator), typeof(SkiaActivityIndicator),
360f, 360.0,
BindingMode.TwoWay); BindingMode.TwoWay);
/// <summary> /// <summary>
@@ -112,45 +131,45 @@ public class SkiaActivityIndicator : SkiaView
/// <summary> /// <summary>
/// Gets or sets the indicator color. /// Gets or sets the indicator color.
/// </summary> /// </summary>
public SKColor Color public Color Color
{ {
get => (SKColor)GetValue(ColorProperty); get => (Color)GetValue(ColorProperty);
set => SetValue(ColorProperty, value); set => SetValue(ColorProperty, value);
} }
/// <summary> /// <summary>
/// Gets or sets the disabled color. /// Gets or sets the disabled color.
/// </summary> /// </summary>
public SKColor DisabledColor public Color DisabledColor
{ {
get => (SKColor)GetValue(DisabledColorProperty); get => (Color)GetValue(DisabledColorProperty);
set => SetValue(DisabledColorProperty, value); set => SetValue(DisabledColorProperty, value);
} }
/// <summary> /// <summary>
/// Gets or sets the indicator size. /// Gets or sets the indicator size.
/// </summary> /// </summary>
public float Size public double Size
{ {
get => (float)GetValue(SizeProperty); get => (double)GetValue(SizeProperty);
set => SetValue(SizeProperty, value); set => SetValue(SizeProperty, value);
} }
/// <summary> /// <summary>
/// Gets or sets the stroke width. /// Gets or sets the stroke width.
/// </summary> /// </summary>
public float StrokeWidth public double StrokeWidth
{ {
get => (float)GetValue(StrokeWidthProperty); get => (double)GetValue(StrokeWidthProperty);
set => SetValue(StrokeWidthProperty, value); set => SetValue(StrokeWidthProperty, value);
} }
/// <summary> /// <summary>
/// Gets or sets the rotation speed in degrees per second. /// Gets or sets the rotation speed in degrees per second.
/// </summary> /// </summary>
public float RotationSpeed public double RotationSpeed
{ {
get => (float)GetValue(RotationSpeedProperty); get => (double)GetValue(RotationSpeedProperty);
set => SetValue(RotationSpeedProperty, value); set => SetValue(RotationSpeedProperty, value);
} }
@@ -165,9 +184,15 @@ public class SkiaActivityIndicator : SkiaView
#endregion #endregion
#region Private Fields
private float _rotationAngle; private float _rotationAngle;
private DateTime _lastUpdateTime = DateTime.UtcNow; private DateTime _lastUpdateTime = DateTime.UtcNow;
#endregion
#region Event Handlers
private void OnIsRunningChanged() private void OnIsRunningChanged()
{ {
if (IsRunning) if (IsRunning)
@@ -177,6 +202,10 @@ public class SkiaActivityIndicator : SkiaView
Invalidate(); Invalidate();
} }
#endregion
#region Rendering
protected override void OnDraw(SKCanvas canvas, SKRect bounds) protected override void OnDraw(SKCanvas canvas, SKRect bounds)
{ {
if (!IsRunning && !IsEnabled) if (!IsRunning && !IsEnabled)
@@ -184,9 +213,13 @@ public class SkiaActivityIndicator : SkiaView
return; return;
} }
var size = (float)Size;
var strokeWidth = (float)StrokeWidth;
var rotationSpeed = (float)RotationSpeed;
var centerX = bounds.MidX; var centerX = bounds.MidX;
var centerY = bounds.MidY; 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 // Update rotation
if (IsRunning) if (IsRunning)
@@ -194,27 +227,27 @@ public class SkiaActivityIndicator : SkiaView
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var elapsed = (now - _lastUpdateTime).TotalSeconds; var elapsed = (now - _lastUpdateTime).TotalSeconds;
_lastUpdateTime = now; _lastUpdateTime = now;
_rotationAngle = (_rotationAngle + (float)(RotationSpeed * elapsed)) % 360; _rotationAngle = (_rotationAngle + (float)(rotationSpeed * elapsed)) % 360;
} }
canvas.Save(); canvas.Save();
canvas.Translate(centerX, centerY); canvas.Translate(centerX, centerY);
canvas.RotateDegrees(_rotationAngle); canvas.RotateDegrees(_rotationAngle);
var color = IsEnabled ? Color : DisabledColor; var colorSK = ToSKColor(IsEnabled ? Color : DisabledColor);
// Draw arcs with varying opacity // Draw arcs with varying opacity
for (int i = 0; i < ArcCount; i++) for (int i = 0; i < ArcCount; i++)
{ {
var alpha = (byte)(255 * (1 - (float)i / ArcCount)); var alpha = (byte)(255 * (1 - (float)i / ArcCount));
var arcColor = color.WithAlpha(alpha); var arcColor = colorSK.WithAlpha(alpha);
using var paint = new SKPaint using var paint = new SKPaint
{ {
Color = arcColor, Color = arcColor,
IsAntialias = true, IsAntialias = true,
Style = SKPaintStyle.Stroke, Style = SKPaintStyle.Stroke,
StrokeWidth = StrokeWidth, StrokeWidth = strokeWidth,
StrokeCap = SKStrokeCap.Round 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) 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
} }