diff --git a/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml b/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml index deeeada..596f612 100644 --- a/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml +++ b/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml @@ -165,12 +165,7 @@ Margin="0"> - + diff --git a/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs b/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs index d2af457..8e9f57a 100644 --- a/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs +++ b/AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs @@ -2,22 +2,189 @@ using ReactiveUI.Avalonia; using Avalonia.Markup.Xaml; using AuroraDesk.Presentation.ViewModels.Pages; using ReactiveUI; +using System; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using Avalonia.Controls; +using Avalonia.Threading; +using Avalonia.Skia.Lottie; +using Avalonia.VisualTree; +using Avalonia.Interactivity; namespace AuroraDesk.Presentation.Views.Pages; public partial class BreathePageView : ReactiveUserControl { + private Lottie? _lottieControl; + private ContentControl? _lottieContainer; + private bool _isDisposed; + private IDisposable? _repeatCountSubscription; + public BreathePageView() { InitializeComponent(); - // 若后续需要生命周期 Hook,可在此处使用 WhenActivated - this.WhenActivated(_ => { }); + this.WhenActivated(disposables => + { + // 重置停用标志 + _isDisposed = false; + + // 查找容器控件 + _lottieContainer = this.FindControl("LottieContainer"); + + if (_lottieContainer == null || ViewModel == null) + { + return; + } + + // 创建新的 Lottie 控件实例 + CreateLottieControl(disposables); + + // 订阅动画路径变化 + var pathSubscription = ViewModel + .WhenAnyValue(x => x.AnimationPath) + .Where(path => !string.IsNullOrEmpty(path)) + .DistinctUntilChanged() + .ObserveOn(AvaloniaScheduler.Instance) + .Subscribe(path => + { + if (_isDisposed || _lottieControl == null) + { + return; + } + + try + { + Dispatcher.UIThread.Post(() => + { + if (!_isDisposed && _lottieControl != null && !string.IsNullOrEmpty(path)) + { + try + { + _lottieControl.Path = path; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to set animation path: {ex.GetType().Name} - {ex.Message}"); + } + } + }, DispatcherPriority.Normal); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to update animation path: {ex.GetType().Name} - {ex.Message}"); + } + }); + + disposables.Add(pathSubscription); + + // 页面停用时的清理 + disposables.Add(Disposable.Create(() => + { + _isDisposed = true; + _repeatCountSubscription?.Dispose(); + _repeatCountSubscription = null; + DestroyLottieControl(); + })); + }); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + private void CreateLottieControl(CompositeDisposable disposables) + { + if (_lottieContainer == null || ViewModel == null || _isDisposed) + { + return; + } + + try + { + // 获取 baseUri(用于资源加载) + // 使用程序集资源的基础 URI + var baseUri = new Uri("avares://AuroraDesk.Presentation/"); + + // 创建新的 Lottie 控件实例 + _lottieControl = new Lottie(baseUri) + { + RepeatCount = ViewModel.RepeatCount, + Stretch = Avalonia.Media.Stretch.Uniform, + HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, + VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, + ClipToBounds = false + }; + + // 设置初始路径 + if (!string.IsNullOrEmpty(ViewModel.AnimationPath)) + { + _lottieControl.Path = ViewModel.AnimationPath; + } + + // 订阅 RepeatCount 变化 + if (ViewModel != null) + { + _repeatCountSubscription = ViewModel.WhenAnyValue(x => x.RepeatCount) + .Subscribe(count => + { + if (_lottieControl != null && !_isDisposed) + { + Dispatcher.UIThread.Post(() => + { + if (_lottieControl != null && !_isDisposed) + { + _lottieControl.RepeatCount = count; + } + }); + } + }); + + if (_repeatCountSubscription != null) + { + disposables.Add(_repeatCountSubscription); + } + } + + // 将控件添加到容器 + _lottieContainer.Content = _lottieControl; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to create Lottie control: {ex.GetType().Name} - {ex.Message}"); + } + } + + private void DestroyLottieControl() + { + try + { + if (_lottieContainer != null) + { + // 清空容器内容,这会触发 Lottie 控件的卸载和资源清理 + Dispatcher.UIThread.Post(() => + { + try + { + if (_lottieContainer != null) + { + _lottieContainer.Content = null; + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to clear container: {ex.GetType().Name} - {ex.Message}"); + } + }, DispatcherPriority.Normal); + } + + _lottieControl = null; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[BreathePageView] Failed to destroy Lottie control: {ex.GetType().Name} - {ex.Message}"); + } + } } diff --git a/ilspy_lottie/Avalonia.Skia.Lottie.csproj b/ilspy_lottie/Avalonia.Skia.Lottie.csproj new file mode 100644 index 0000000..b803e15 --- /dev/null +++ b/ilspy_lottie/Avalonia.Skia.Lottie.csproj @@ -0,0 +1,22 @@ + + + Avalonia.Skia.Lottie + False + netcoreapp7.0 + + + 12.0 + True + + + + + + + + + + + + + \ No newline at end of file diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/Lottie.cs b/ilspy_lottie/Avalonia.Skia.Lottie/Lottie.cs new file mode 100644 index 0000000..caaa597 --- /dev/null +++ b/ilspy_lottie/Avalonia.Skia.Lottie/Lottie.cs @@ -0,0 +1,397 @@ +using System; +using System.IO; +using System.Numerics; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Logging; +using Avalonia.Media; +using Avalonia.Metadata; +using Avalonia.Platform; +using Avalonia.Rendering.Composition; +using SkiaSharp; +using SkiaSharp.SceneGraph; +using SkiaSharp.Skottie; + +namespace Avalonia.Skia.Lottie; + +public class Lottie : Control +{ + private Animation? _animation; + + private int _repeatCount; + + private readonly Uri _baseUri; + + private string? _preloadPath; + + private CompositionCustomVisual? _customVisual; + + public const int Infinity = -1; + + public static readonly StyledProperty PathProperty = AvaloniaProperty.Register("Path", (string)null, false, (BindingMode)1, (Func)null, (Func)null, false); + + public static readonly StyledProperty StretchProperty = AvaloniaProperty.Register("Stretch", (Stretch)2, false, (BindingMode)1, (Func)null, (Func)null, false); + + public static readonly StyledProperty StretchDirectionProperty = AvaloniaProperty.Register("StretchDirection", (StretchDirection)2, false, (BindingMode)1, (Func)null, (Func)null, false); + + public static readonly StyledProperty RepeatCountProperty = AvaloniaProperty.Register("RepeatCount", -1, false, (BindingMode)1, (Func)null, (Func)null, false); + + [Content] + public string? Path + { + get + { + return ((AvaloniaObject)this).GetValue(PathProperty); + } + set + { + ((AvaloniaObject)this).SetValue(PathProperty, value, (BindingPriority)0); + } + } + + public Stretch Stretch + { + get + { + //IL_0006: Unknown result type (might be due to invalid IL or missing references) + return ((AvaloniaObject)this).GetValue(StretchProperty); + } + set + { + //IL_0006: Unknown result type (might be due to invalid IL or missing references) + ((AvaloniaObject)this).SetValue(StretchProperty, value, (BindingPriority)0); + } + } + + public StretchDirection StretchDirection + { + get + { + //IL_0006: Unknown result type (might be due to invalid IL or missing references) + return ((AvaloniaObject)this).GetValue(StretchDirectionProperty); + } + set + { + //IL_0006: Unknown result type (might be due to invalid IL or missing references) + ((AvaloniaObject)this).SetValue(StretchDirectionProperty, value, (BindingPriority)0); + } + } + + public int RepeatCount + { + get + { + return ((AvaloniaObject)this).GetValue(RepeatCountProperty); + } + set + { + ((AvaloniaObject)this).SetValue(RepeatCountProperty, value, (BindingPriority)0); + } + } + + public Lottie(Uri baseUri) + { + _baseUri = baseUri; + } + + public Lottie(IServiceProvider serviceProvider) + { + _baseUri = serviceProvider.GetContextBaseUri(); + } + + protected override void OnLoaded(RoutedEventArgs routedEventArgs) + { + //IL_006f: Unknown result type (might be due to invalid IL or missing references) + //IL_0074: Unknown result type (might be due to invalid IL or missing references) + //IL_0077: Unknown result type (might be due to invalid IL or missing references) + //IL_007c: Unknown result type (might be due to invalid IL or missing references) + //IL_0086: Unknown result type (might be due to invalid IL or missing references) + //IL_008b: Unknown result type (might be due to invalid IL or missing references) + //IL_008e: Unknown result type (might be due to invalid IL or missing references) + //IL_0093: Unknown result type (might be due to invalid IL or missing references) + //IL_00a1: Unknown result type (might be due to invalid IL or missing references) + //IL_00b9: Unknown result type (might be due to invalid IL or missing references) + //IL_00c4: Unknown result type (might be due to invalid IL or missing references) + ((Control)this).OnLoaded(routedEventArgs); + CompositionVisual elementVisual = ElementComposition.GetElementVisual((Visual)(object)this); + Compositor val = ((elementVisual != null) ? ((CompositionObject)elementVisual).Compositor : null); + if (val != null) + { + _customVisual = val.CreateCustomVisual((CompositionCustomVisualHandler)(object)new LottieCompositionCustomVisualHandler()); + ElementComposition.SetElementChildVisual((Visual)(object)this, (CompositionVisual)(object)_customVisual); + ((Layoutable)this).LayoutUpdated += OnLayoutUpdated; + if (_preloadPath != null) + { + DisposeImpl(); + Load(_preloadPath); + CompositionCustomVisual? customVisual = _customVisual; + Rect bounds = ((Visual)this).Bounds; + Size size = ((Rect)(ref bounds)).Size; + float x = (float)((Size)(ref size)).Width; + bounds = ((Visual)this).Bounds; + size = ((Rect)(ref bounds)).Size; + ((CompositionVisual)customVisual).Size = Vector.op_Implicit(new Vector2(x, (float)((Size)(ref size)).Height)); + _customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Update, _animation, Stretch, StretchDirection)); + Start(); + _preloadPath = null; + } + } + } + + protected override void OnUnloaded(RoutedEventArgs routedEventArgs) + { + ((Control)this).OnUnloaded(routedEventArgs); + ((Layoutable)this).LayoutUpdated -= OnLayoutUpdated; + Stop(); + DisposeImpl(); + } + + private void OnLayoutUpdated(object? sender, EventArgs e) + { + //IL_0010: Unknown result type (might be due to invalid IL or missing references) + //IL_0015: Unknown result type (might be due to invalid IL or missing references) + //IL_0018: Unknown result type (might be due to invalid IL or missing references) + //IL_001d: Unknown result type (might be due to invalid IL or missing references) + //IL_0027: Unknown result type (might be due to invalid IL or missing references) + //IL_002c: Unknown result type (might be due to invalid IL or missing references) + //IL_002f: Unknown result type (might be due to invalid IL or missing references) + //IL_0034: Unknown result type (might be due to invalid IL or missing references) + //IL_0042: Unknown result type (might be due to invalid IL or missing references) + //IL_005a: Unknown result type (might be due to invalid IL or missing references) + //IL_0065: Unknown result type (might be due to invalid IL or missing references) + if (_customVisual != null) + { + CompositionCustomVisual? customVisual = _customVisual; + Rect bounds = ((Visual)this).Bounds; + Size size = ((Rect)(ref bounds)).Size; + float x = (float)((Size)(ref size)).Width; + bounds = ((Visual)this).Bounds; + size = ((Rect)(ref bounds)).Size; + ((CompositionVisual)customVisual).Size = Vector.op_Implicit(new Vector2(x, (float)((Size)(ref size)).Height)); + _customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Update, _animation, Stretch, StretchDirection)); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + //IL_000a: Unknown result type (might be due to invalid IL or missing references) + //IL_0010: Unknown result type (might be due to invalid IL or missing references) + //IL_002b: Unknown result type (might be due to invalid IL or missing references) + //IL_0030: Unknown result type (might be due to invalid IL or missing references) + //IL_003f: Unknown result type (might be due to invalid IL or missing references) + //IL_0044: Unknown result type (might be due to invalid IL or missing references) + //IL_004d: Unknown result type (might be due to invalid IL or missing references) + //IL_001c: Unknown result type (might be due to invalid IL or missing references) + //IL_0022: Unknown result type (might be due to invalid IL or missing references) + //IL_0052: Unknown result type (might be due to invalid IL or missing references) + //IL_0054: Unknown result type (might be due to invalid IL or missing references) + //IL_0059: Unknown result type (might be due to invalid IL or missing references) + //IL_005a: Unknown result type (might be due to invalid IL or missing references) + //IL_005c: Unknown result type (might be due to invalid IL or missing references) + //IL_0061: Unknown result type (might be due to invalid IL or missing references) + if (_animation == null) + { + return default(Size); + } + _003F val; + if (_animation == null) + { + val = default(Size); + } + else + { + SKSize size = _animation.Size; + double num = ((SKSize)(ref size)).Width; + size = _animation.Size; + val = new Size(num, (double)((SKSize)(ref size)).Height); + } + Size val2 = (Size)val; + return MediaExtensions.CalculateSize(Stretch, availableSize, val2, StretchDirection); + } + + protected override Size ArrangeOverride(Size finalSize) + { + //IL_000a: Unknown result type (might be due to invalid IL or missing references) + //IL_0010: Unknown result type (might be due to invalid IL or missing references) + //IL_002b: Unknown result type (might be due to invalid IL or missing references) + //IL_0030: Unknown result type (might be due to invalid IL or missing references) + //IL_003f: Unknown result type (might be due to invalid IL or missing references) + //IL_0044: Unknown result type (might be due to invalid IL or missing references) + //IL_004d: Unknown result type (might be due to invalid IL or missing references) + //IL_001c: Unknown result type (might be due to invalid IL or missing references) + //IL_0022: Unknown result type (might be due to invalid IL or missing references) + //IL_0052: Unknown result type (might be due to invalid IL or missing references) + //IL_0054: Unknown result type (might be due to invalid IL or missing references) + //IL_0059: Unknown result type (might be due to invalid IL or missing references) + //IL_005a: Unknown result type (might be due to invalid IL or missing references) + //IL_005c: Unknown result type (might be due to invalid IL or missing references) + if (_animation == null) + { + return default(Size); + } + _003F val; + if (_animation == null) + { + val = default(Size); + } + else + { + SKSize size = _animation.Size; + double num = ((SKSize)(ref size)).Width; + size = _animation.Size; + val = new Size(num, (double)((SKSize)(ref size)).Height); + } + Size val2 = (Size)val; + return MediaExtensions.CalculateSize(Stretch, finalSize, val2, (StretchDirection)2); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + ((Control)this).OnPropertyChanged(change); + string name = change.Property.Name; + if (!(name == "Path")) + { + if (name == "RepeatCount") + { + _repeatCount = AvaloniaPropertyChangedExtensions.GetNewValue(change); + Stop(); + Start(); + } + } + else + { + string newValue = AvaloniaPropertyChangedExtensions.GetNewValue(change); + if (_preloadPath == null && _customVisual == null) + { + _preloadPath = newValue; + } + else + { + Load(newValue); + } + } + } + + private Animation? Load(Stream stream) + { + //IL_0001: Unknown result type (might be due to invalid IL or missing references) + //IL_0007: Expected O, but got Unknown + //IL_00f7: Unknown result type (might be due to invalid IL or missing references) + //IL_00fc: Unknown result type (might be due to invalid IL or missing references) + //IL_0040: Unknown result type (might be due to invalid IL or missing references) + //IL_0045: Unknown result type (might be due to invalid IL or missing references) + SKManagedStream val = new SKManagedStream(stream); + try + { + Animation val2 = default(Animation); + ParametrizedLogger valueOrDefault; + if (Animation.TryCreate((SKStream)(object)val, ref val2)) + { + val2.Seek(0.0, (InvalidationController)null); + ParametrizedLogger? val3 = Logger.TryGet((LogEventLevel)2, "Control"); + if (val3.HasValue) + { + valueOrDefault = val3.GetValueOrDefault(); + ((ParametrizedLogger)(ref valueOrDefault)).Log((object)this, $"Version: {val2.Version} Duration: {val2.Duration} Fps:{val2.Fps} InPoint: {val2.InPoint} OutPoint: {val2.OutPoint}"); + } + } + else + { + ParametrizedLogger? val3 = Logger.TryGet((LogEventLevel)3, "Control"); + if (val3.HasValue) + { + valueOrDefault = val3.GetValueOrDefault(); + ((ParametrizedLogger)(ref valueOrDefault)).Log((object)this, "Failed to load animation."); + } + } + return val2; + } + finally + { + ((IDisposable)val)?.Dispose(); + } + } + + private Animation? Load(string path, Uri? baseUri) + { + Uri uri = (path.StartsWith("/") ? new Uri(path, UriKind.Relative) : new Uri(path, UriKind.RelativeOrAbsolute)); + if (uri.IsAbsoluteUri && uri.IsFile) + { + using (FileStream stream = File.OpenRead(uri.LocalPath)) + { + return Load(stream); + } + } + using Stream stream2 = AssetLoader.Open(uri, baseUri); + if (stream2 == null) + { + return null; + } + return Load(stream2); + } + + private void Load(string? path) + { + //IL_006d: Unknown result type (might be due to invalid IL or missing references) + //IL_0072: Unknown result type (might be due to invalid IL or missing references) + Stop(); + if (path == null) + { + DisposeImpl(); + return; + } + DisposeImpl(); + try + { + _repeatCount = RepeatCount; + _animation = Load(path, _baseUri); + if (_animation != null) + { + ((Layoutable)this).InvalidateArrange(); + ((Layoutable)this).InvalidateMeasure(); + Start(); + } + } + catch (Exception ex) + { + ParametrizedLogger? val = Logger.TryGet((LogEventLevel)3, "Control"); + if (val.HasValue) + { + ParametrizedLogger valueOrDefault = val.GetValueOrDefault(); + ((ParametrizedLogger)(ref valueOrDefault)).Log((object)this, "Failed to load animation: " + ex); + } + _animation = null; + } + } + + private void Start() + { + //IL_0013: Unknown result type (might be due to invalid IL or missing references) + //IL_001e: Unknown result type (might be due to invalid IL or missing references) + CompositionCustomVisual? customVisual = _customVisual; + if (customVisual != null) + { + customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Start, _animation, Stretch, StretchDirection, _repeatCount)); + } + } + + private void Stop() + { + CompositionCustomVisual? customVisual = _customVisual; + if (customVisual != null) + { + customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Stop)); + } + } + + private void DisposeImpl() + { + CompositionCustomVisual? customVisual = _customVisual; + if (customVisual != null) + { + customVisual.SendHandlerMessage((object)new LottiePayload(LottieCommand.Dispose)); + } + } +} diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/LottieCommand.cs b/ilspy_lottie/Avalonia.Skia.Lottie/LottieCommand.cs new file mode 100644 index 0000000..bd2de33 --- /dev/null +++ b/ilspy_lottie/Avalonia.Skia.Lottie/LottieCommand.cs @@ -0,0 +1,9 @@ +namespace Avalonia.Skia.Lottie; + +internal enum LottieCommand +{ + Start, + Stop, + Update, + Dispose +} diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/LottieCompositionCustomVisualHandler.cs b/ilspy_lottie/Avalonia.Skia.Lottie/LottieCompositionCustomVisualHandler.cs new file mode 100644 index 0000000..0e70205 --- /dev/null +++ b/ilspy_lottie/Avalonia.Skia.Lottie/LottieCompositionCustomVisualHandler.cs @@ -0,0 +1,368 @@ +using System; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering.Composition; +using SkiaSharp; +using SkiaSharp.SceneGraph; +using SkiaSharp.Skottie; + +namespace Avalonia.Skia.Lottie; + +internal class LottieCompositionCustomVisualHandler : CompositionCustomVisualHandler +{ + private TimeSpan _primaryTimeElapsed; + + private TimeSpan _animationElapsed; + + private TimeSpan? _lastServerTime; + + private bool _running; + + private Animation? _animation; + + private Stretch? _stretch; + + private StretchDirection? _stretchDirection; + + private InvalidationController? _ic; + + private readonly object _sync = new object(); + + private int _repeatCount; + + private int _count; + + public override void OnMessage(object message) + { + //IL_00b2: Unknown result type (might be due to invalid IL or missing references) + //IL_00b7: Unknown result type (might be due to invalid IL or missing references) + //IL_0058: Unknown result type (might be due to invalid IL or missing references) + //IL_005d: Unknown result type (might be due to invalid IL or missing references) + //IL_00cf: Unknown result type (might be due to invalid IL or missing references) + //IL_00d4: Unknown result type (might be due to invalid IL or missing references) + //IL_0075: Unknown result type (might be due to invalid IL or missing references) + //IL_007a: Unknown result type (might be due to invalid IL or missing references) + //IL_012a: Unknown result type (might be due to invalid IL or missing references) + //IL_012b: Unknown result type (might be due to invalid IL or missing references) + //IL_012d: Unknown result type (might be due to invalid IL or missing references) + //IL_012e: Unknown result type (might be due to invalid IL or missing references) + //IL_0131: Unknown result type (might be due to invalid IL or missing references) + //IL_013e: Unknown result type (might be due to invalid IL or missing references) + //IL_00eb: Unknown result type (might be due to invalid IL or missing references) + //IL_00f7: Unknown result type (might be due to invalid IL or missing references) + if (!(message is LottiePayload lottiePayload)) + { + return; + } + switch (lottiePayload.LottieCommand) + { + case LottieCommand.Start: + { + Animation animation = lottiePayload.Animation; + if (animation == null) + { + break; + } + Stretch? stretch = lottiePayload.Stretch; + if (!stretch.HasValue) + { + break; + } + Stretch valueOrDefault = stretch.GetValueOrDefault(); + StretchDirection? stretchDirection = lottiePayload.StretchDirection; + if (stretchDirection.HasValue) + { + StretchDirection valueOrDefault2 = stretchDirection.GetValueOrDefault(); + int? repeatCount = lottiePayload.RepeatCount; + if (repeatCount.HasValue) + { + int valueOrDefault3 = repeatCount.GetValueOrDefault(); + _running = true; + _lastServerTime = null; + _stretch = valueOrDefault; + _stretchDirection = valueOrDefault2; + _animation = animation; + _repeatCount = valueOrDefault3; + _count = 0; + _animationElapsed = TimeSpan.Zero; + ((CompositionCustomVisualHandler)this).RegisterForNextAnimationFrameUpdate(); + } + } + break; + } + case LottieCommand.Update: + { + Stretch? stretch = lottiePayload.Stretch; + if (stretch.HasValue) + { + Stretch valueOrDefault = stretch.GetValueOrDefault(); + StretchDirection? stretchDirection = lottiePayload.StretchDirection; + if (stretchDirection.HasValue) + { + StretchDirection valueOrDefault2 = stretchDirection.GetValueOrDefault(); + Stretch value = valueOrDefault; + StretchDirection value2 = valueOrDefault2; + _stretch = value; + _stretchDirection = value2; + ((CompositionCustomVisualHandler)this).RegisterForNextAnimationFrameUpdate(); + } + } + break; + } + case LottieCommand.Stop: + _running = false; + _animationElapsed = TimeSpan.Zero; + _count = 0; + break; + case LottieCommand.Dispose: + DisposeImpl(); + break; + } + } + + public override void OnAnimationFrameUpdate() + { + if (_running) + { + if (_repeatCount == 0 || (_repeatCount > 0 && _count >= _repeatCount)) + { + _running = false; + _animationElapsed = TimeSpan.Zero; + } + ((CompositionCustomVisualHandler)this).Invalidate(); + ((CompositionCustomVisualHandler)this).RegisterForNextAnimationFrameUpdate(); + } + } + + private void DisposeImpl() + { + lock (_sync) + { + Animation? animation = _animation; + if (animation != null) + { + ((SKNativeObject)animation).Dispose(); + } + _animation = null; + InvalidationController? ic = _ic; + if (ic != null) + { + ic.End(); + } + InvalidationController? ic2 = _ic; + if (ic2 != null) + { + ((SKNativeObject)ic2).Dispose(); + } + _ic = null; + } + } + + private double GetFrameTime() + { + if (_animation == null) + { + return 0.0; + } + double totalSeconds = _animationElapsed.TotalSeconds; + if (_animationElapsed.TotalSeconds > _animation.Duration.TotalSeconds) + { + _animationElapsed = TimeSpan.Zero; + InvalidationController? ic = _ic; + if (ic != null) + { + ic.End(); + } + InvalidationController? ic2 = _ic; + if (ic2 != null) + { + ic2.Begin(); + } + _count++; + } + return totalSeconds; + } + + private void Draw(SKCanvas canvas) + { + //IL_0014: Unknown result type (might be due to invalid IL or missing references) + //IL_001e: Expected O, but got Unknown + //IL_0067: Unknown result type (might be due to invalid IL or missing references) + //IL_006c: Unknown result type (might be due to invalid IL or missing references) + //IL_0076: Unknown result type (might be due to invalid IL or missing references) + //IL_007b: Unknown result type (might be due to invalid IL or missing references) + //IL_009a: Unknown result type (might be due to invalid IL or missing references) + Animation animation = _animation; + if (animation == null) + { + return; + } + if (_ic == null) + { + _ic = new InvalidationController(); + _ic.Begin(); + } + InvalidationController ic = _ic; + if (_repeatCount != 0) + { + double num = GetFrameTime(); + if (!_running) + { + num = (float)animation.Duration.TotalSeconds; + } + SKSize size = animation.Size; + float width = ((SKSize)(ref size)).Width; + size = animation.Size; + SKRect val = default(SKRect); + ((SKRect)(ref val))._002Ector(0f, 0f, width, ((SKSize)(ref size)).Height); + animation.SeekFrameTime(num, ic); + canvas.Save(); + animation.Render(canvas, val); + canvas.Restore(); + ic.Reset(); + } + } + + public unsafe override void OnRender(ImmediateDrawingContext context) + { + //IL_0092: Unknown result type (might be due to invalid IL or missing references) + //IL_0097: Unknown result type (might be due to invalid IL or missing references) + //IL_00ab: Unknown result type (might be due to invalid IL or missing references) + //IL_00b0: Unknown result type (might be due to invalid IL or missing references) + //IL_00cb: Unknown result type (might be due to invalid IL or missing references) + //IL_00d0: Unknown result type (might be due to invalid IL or missing references) + //IL_00d6: Unknown result type (might be due to invalid IL or missing references) + //IL_00e3: Unknown result type (might be due to invalid IL or missing references) + //IL_00e8: Unknown result type (might be due to invalid IL or missing references) + //IL_00f3: Unknown result type (might be due to invalid IL or missing references) + //IL_00f8: Unknown result type (might be due to invalid IL or missing references) + //IL_0130: Unknown result type (might be due to invalid IL or missing references) + //IL_0133: Unknown result type (might be due to invalid IL or missing references) + //IL_0138: Unknown result type (might be due to invalid IL or missing references) + //IL_013a: Unknown result type (might be due to invalid IL or missing references) + //IL_013c: Unknown result type (might be due to invalid IL or missing references) + //IL_0141: Unknown result type (might be due to invalid IL or missing references) + //IL_0143: Unknown result type (might be due to invalid IL or missing references) + //IL_0145: Unknown result type (might be due to invalid IL or missing references) + //IL_0147: Unknown result type (might be due to invalid IL or missing references) + //IL_014c: Unknown result type (might be due to invalid IL or missing references) + //IL_0150: Unknown result type (might be due to invalid IL or missing references) + //IL_0152: Unknown result type (might be due to invalid IL or missing references) + //IL_0157: Unknown result type (might be due to invalid IL or missing references) + //IL_015c: Unknown result type (might be due to invalid IL or missing references) + //IL_0160: Unknown result type (might be due to invalid IL or missing references) + //IL_0162: Unknown result type (might be due to invalid IL or missing references) + //IL_0167: Unknown result type (might be due to invalid IL or missing references) + //IL_0169: Unknown result type (might be due to invalid IL or missing references) + //IL_016b: Unknown result type (might be due to invalid IL or missing references) + //IL_0170: Unknown result type (might be due to invalid IL or missing references) + //IL_0176: Unknown result type (might be due to invalid IL or missing references) + //IL_017b: Unknown result type (might be due to invalid IL or missing references) + //IL_017d: Unknown result type (might be due to invalid IL or missing references) + //IL_0182: Unknown result type (might be due to invalid IL or missing references) + //IL_0187: Unknown result type (might be due to invalid IL or missing references) + //IL_018c: Unknown result type (might be due to invalid IL or missing references) + //IL_0190: Unknown result type (might be due to invalid IL or missing references) + //IL_0196: Unknown result type (might be due to invalid IL or missing references) + //IL_0199: Unknown result type (might be due to invalid IL or missing references) + //IL_019e: Unknown result type (might be due to invalid IL or missing references) + //IL_01a3: Unknown result type (might be due to invalid IL or missing references) + //IL_01c3: Unknown result type (might be due to invalid IL or missing references) + //IL_01c8: Unknown result type (might be due to invalid IL or missing references) + //IL_01fc: Unknown result type (might be due to invalid IL or missing references) + //IL_0201: Unknown result type (might be due to invalid IL or missing references) + //IL_0204: Unknown result type (might be due to invalid IL or missing references) + //IL_0206: Unknown result type (might be due to invalid IL or missing references) + //IL_020b: Unknown result type (might be due to invalid IL or missing references) + //IL_020e: Unknown result type (might be due to invalid IL or missing references) + //IL_0210: Unknown result type (might be due to invalid IL or missing references) + //IL_0212: Unknown result type (might be due to invalid IL or missing references) + //IL_0217: Unknown result type (might be due to invalid IL or missing references) + //IL_021c: Unknown result type (might be due to invalid IL or missing references) + lock (_sync) + { + if (_running) + { + if (_lastServerTime.HasValue) + { + TimeSpan timeSpan = ((CompositionCustomVisualHandler)this).CompositionNow - _lastServerTime.Value; + _primaryTimeElapsed += timeSpan; + _animationElapsed += timeSpan; + } + _lastServerTime = ((CompositionCustomVisualHandler)this).CompositionNow; + } + Animation animation = _animation; + if (animation == null) + { + return; + } + Stretch? stretch = _stretch; + if (!stretch.HasValue) + { + return; + } + Stretch valueOrDefault = stretch.GetValueOrDefault(); + StretchDirection? stretchDirection = _stretchDirection; + if (!stretchDirection.HasValue) + { + return; + } + StretchDirection valueOrDefault2 = stretchDirection.GetValueOrDefault(); + ISkiaSharpApiLeaseFeature val = OptionalFeatureProviderExtensions.TryGetFeature((IOptionalFeatureProvider)(object)context); + if (val == null) + { + return; + } + Rect renderBounds = ((CompositionCustomVisualHandler)this).GetRenderBounds(); + Rect val2 = default(Rect); + ((Rect)(ref val2))._002Ector(((Rect)(ref renderBounds)).Size); + SKSize size = animation.Size; + double num = ((SKSize)(ref size)).Width; + size = animation.Size; + Size val3 = default(Size); + ((Size)(ref val3))._002Ector(num, (double)((SKSize)(ref size)).Height); + if (((Size)(ref val3)).Width <= 0.0 || ((Size)(ref val3)).Height <= 0.0) + { + return; + } + Vector val4 = MediaExtensions.CalculateScaling(valueOrDefault, ((Rect)(ref renderBounds)).Size, val3, valueOrDefault2); + Size val5 = val3 * val4; + Rect val6 = ((Rect)(ref val2)).CenterRect(new Rect(val5)); + Rect val7 = ((Rect)(ref val6)).Intersect(val2); + val6 = new Rect(val3); + Rect val8 = ((Rect)(ref val6)).CenterRect(new Rect(((Rect)(ref val7)).Size / val4)); + SKRect val9 = SKRect.Create(default(SKPoint), animation.Size); + Matrix val10 = Matrix.CreateScale(((Rect)(ref val7)).Width / ((Rect)(ref val8)).Width, ((Rect)(ref val7)).Height / ((Rect)(ref val8)).Height); + Matrix val11 = Matrix.CreateTranslation(0.0 - ((Rect)(ref val8)).X + ((Rect)(ref val7)).X - (double)((SKRect)(ref val9)).Top, 0.0 - ((Rect)(ref val8)).Y + ((Rect)(ref val7)).Y - (double)((SKRect)(ref val9)).Left); + PushedState val12 = context.PushClip(val7); + try + { + PushedState val13 = context.PushPostTransform(val11 * val10); + try + { + ISkiaSharpApiLease val14 = val.Lease(); + try + { + SKCanvas val15 = ((val14 != null) ? val14.SkCanvas : null); + if (val15 != null) + { + Draw(val15); + } + } + finally + { + ((IDisposable)val14)?.Dispose(); + } + } + finally + { + ((IDisposable)(*(PushedState*)(&val13))/*cast due to .constrained prefix*/).Dispose(); + } + } + finally + { + ((IDisposable)(*(PushedState*)(&val12))/*cast due to .constrained prefix*/).Dispose(); + } + } + } +} diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/LottiePayload.cs b/ilspy_lottie/Avalonia.Skia.Lottie/LottiePayload.cs new file mode 100644 index 0000000..4664b33 --- /dev/null +++ b/ilspy_lottie/Avalonia.Skia.Lottie/LottiePayload.cs @@ -0,0 +1,6 @@ +using Avalonia.Media; +using SkiaSharp.Skottie; + +namespace Avalonia.Skia.Lottie; + +internal record struct LottiePayload(LottieCommand LottieCommand, Animation? Animation = null, Stretch? Stretch = null, StretchDirection? StretchDirection = null, int? RepeatCount = null); diff --git a/ilspy_lottie/Avalonia.Skia.Lottie/ServiceProviderExtensions.cs b/ilspy_lottie/Avalonia.Skia.Lottie/ServiceProviderExtensions.cs new file mode 100644 index 0000000..7a7f63f --- /dev/null +++ b/ilspy_lottie/Avalonia.Skia.Lottie/ServiceProviderExtensions.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Markup.Xaml; + +namespace Avalonia.Skia.Lottie; + +internal static class ServiceProviderExtensions +{ + public static T GetService(this IServiceProvider sp) + { + return (T)(sp?.GetService(typeof(T))); + } + + public static Uri GetContextBaseUri(this IServiceProvider ctx) + { + return ctx.GetService().BaseUri; + } +} diff --git a/ilspy_lottie/Properties/AssemblyInfo.cs b/ilspy_lottie/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8193002 --- /dev/null +++ b/ilspy_lottie/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; +using Avalonia.Metadata; + +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Skia.Lottie")] +[assembly: AssemblyCompany("Wiesław Šoltés")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCopyright("Copyright © Wiesław Šoltés 2023")] +[assembly: AssemblyDescription("An lottie animation player control for Avalonia.")] +[assembly: AssemblyFileVersion("11.0.0.0")] +[assembly: AssemblyInformationalVersion("11.0.0")] +[assembly: AssemblyProduct("Avalonia.Skia.Lottie")] +[assembly: AssemblyTitle("Avalonia.Skia.Lottie")] +[assembly: AssemblyVersion("11.0.0.0")] +[module: System.Runtime.CompilerServices.RefSafetyRules(11)] diff --git a/modify.md b/modify.md index 86a30af..b3d4238 100644 --- a/modify.md +++ b/modify.md @@ -2,6 +2,39 @@ ## 2025年修改记录 +### 修复 BreathePageView 页面切换闪退问题 +- **日期**: 2025年1月 +- **问题**: 第一次进入 BreathePageView 没问题,导航到其他页面后再切换回来时出现 `System.ExecutionEngineException` 闪退(HResult: -2146233082) +- **问题分析**: + - ❌ **生命周期管理缺失**: BreathePageView 没有正确处理页面激活/停用生命周期,导致 Lottie 控件资源管理混乱 + - ❌ **XAML 绑定时机问题**: 页面重新激活时,XAML 绑定立即更新 Path 属性,但 Lottie 控件的资源可能还未完全清理 + - ❌ **资源释放冲突**: 当页面重新加载时,控件试图访问已经被释放或正在释放的 Skia 底层资源,导致 ExecutionEngineException + - ❌ **控件实例复用**: 同一个 Lottie 控件实例在页面切换时被重复使用,导致资源状态混乱 +- **修改文件**: + - `AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml` (使用 ContentControl 作为容器) + - `AuroraDesk.Presentation/Views/Pages/BreathePageView.axaml.cs` (动态创建和销毁 Lottie 控件) +- **主要修复**: + - ✅ **条件渲染**: 使用 `ContentControl` 作为容器,不在 XAML 中直接放置 Lottie 控件,改为在代码后台动态创建 + - ✅ **动态创建控件**: 在页面激活时(`WhenActivated`)创建全新的 Lottie 控件实例,确保每次都是干净的状态 + - ✅ **完全销毁控件**: 在页面停用时,通过设置 `ContentControl.Content = null` 完全移除控件,触发控件的 `OnUnloaded` 和资源清理 + - ✅ **生命周期管理**: 使用 `WhenActivated` 管理页面生命周期,确保控件在正确的时机创建和销毁 + - ✅ **订阅管理**: 正确管理 `RepeatCount` 和 `AnimationPath` 的订阅,在停用时自动清理 + - ✅ **线程安全**: 所有 UI 操作都通过 `Dispatcher.UIThread.Post` 在 UI 线程上执行 + - ✅ **异常处理**: 添加 try-catch 捕获所有异常,避免崩溃 +- **技术细节**: + - 在 XAML 中使用 `` 作为容器 + - 在 `CreateLottieControl` 方法中动态创建新的 `Lottie` 控件实例 + - 设置控件的初始属性(RepeatCount、Stretch、对齐方式等) + - 订阅 `ViewModel.RepeatCount` 和 `ViewModel.AnimationPath` 变化 + - 将控件添加到 `ContentControl.Content`,触发加载和初始化 + - 在 `DestroyLottieControl` 中设置 `Content = null`,触发控件的卸载和资源清理 + - 所有订阅都添加到 `CompositeDisposable`,确保在停用时自动清理 +- **效果**: + - ✅ **页面切换正常**: 每次页面激活时创建全新的控件实例,避免了资源冲突 + - ✅ **资源管理正确**: 通过完全移除控件,确保 Skia 资源被正确释放 + - ✅ **异常安全**: 即使出现异常,也不会导致应用崩溃,只会记录日志 + - ✅ **彻底解决**: 通过动态创建和销毁控件,从根本上避免了 ExecutionEngineException + ### 新增文件浏览页面 - **日期**: 2025年11月14日 - **调整内容**: