AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.81k stars 2.23k forks source link

Animation finished event #9771

Open squidink7 opened 1 year ago

squidink7 commented 1 year ago

Is your feature request related to a problem? Please describe. I am trying to create animations for the process of removing controls both in list views but also custom controls such as dialogs and application pages. After the animation completes I would like to delete this control.

Describe the solution you'd like Ideally, I would like either a way to specify keyframes in my animation that execute methods as opposed to traditional setters, or an event that is emitted when an animation completes.

Describe alternatives you've considered I have tried to define the animation in code-behind, however right now the process for creating custom animations is rather cumbersome when done outside of xaml.

Additional context Avalonia is my first introduction to xaml (I've never used WPF), so my approach may be completely wrong. I'm currently going off experience in other frameworks such as CSS/JS and Qt, so if this method of animation is not how xaml frameworks typically work please let me know.

squidink7 commented 1 year ago

Another possibly better solution would be to have the ability to create an animation which runs before the control is removed from the visual tree, as this would mean such animations could be created without altering the logic of the control

OronDF343 commented 8 months ago

The desired behavior is possible, albeit not obvious.

My use case for this feature is to play a fade-out animation when closing a window. In WPF, this is possible using the Timeline.Completed event. Storyboard (the animation object in WPF) inherits from Timeline so it is possible to subscribe to the event to get notified when an animation finishes playing. The animation can even be a resource so it can be themed.

In Avalonia, I have acheived the same effect with a slightly different method. If the animation is a resource, it can be run manually from the code-behind, which returns a task that completes when the animation finishes playing.

In case it is useful, here is what I came up with so far (using Avalonia.Xaml.Interactivity) Note: This code has not been tested extensively

using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Threading;
using Avalonia.Xaml.Interactivity;
using System.Threading.Tasks;

namespace TestUI.Helpers;

public class CloseAnimationBehavior : Behavior<Window>
{
    protected override void OnAttached()
    {
        AssociatedObject.Closing += AssociatedObject_Closing;
        base.OnAttached();
    }

    private void AssociatedObject_Closing(object sender, WindowClosingEventArgs e)
    {
        // If the animation exists, cancel the event and run it to completion before closing.
        if (AssociatedObject.TryFindResource("CloseAnimation", out var resObj) && resObj is Animation anim)
        {
            // Unsubscribe from the event so this logic only happens once
            AssociatedObject.Closing -= AssociatedObject_Closing;
            // Cancel the event so the window doesn't close
            e.Cancel = true;
            // Start the animation on the current (UI) thread
            var animTask = anim.RunAsync(AssociatedObject);
            // Wait for it on a different thread
            Task.Run(async () =>
            {
                // Wait for the animation to complete
                await animTask;
                // Close the window from the UI thread
                Dispatcher.UIThread.Invoke(() => AssociatedObject.Close());
            });
        }
    }
}

My style:

...
  <!-- Window style -->
  <Style Selector="Window.main">
    <Style.Animations>
      <Animation Duration="0:0:0.7" IterationCount="1">
        <KeyFrame Cue="0%">
          <Setter Property="Opacity" Value="0.0" />
        </KeyFrame>
        <KeyFrame Cue="100%">
          <Setter Property="Opacity" Value="1.0" />
        </KeyFrame>
      </Animation>
    </Style.Animations>
    <Style.Resources>
      <Animation x:Key="CloseAnimation" Duration="0:0:0.7" IterationCount="1">
        <KeyFrame Cue="0%">
          <Setter Property="Opacity" Value="1.0" />
        </KeyFrame>
        <KeyFrame Cue="100%">
          <Setter Property="Opacity" Value="0.0" />
        </KeyFrame>
      </Animation>
    </Style.Resources>
  </Style>
...

Relevant parts of the window:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
        xmlns:helpers="clr-namespace:TestUI.Helpers;assembly=TestUI"
        Classes="main">
  <i:Interaction.Behaviors>
    <helpers:CloseAnimationBehavior />
  </i:Interaction.Behaviors>
  ...
</Window>
maxkatz6 commented 8 months ago

Why do you run your animation in a different thread? It's potentially dangerous.

OronDF343 commented 8 months ago

I am only waiting for it to complete from a different thread.

On February 28, 2024 3:15:50 AM GMT+02:00, Max Katz @.***> wrote:

Why do you run your animation in a different thread? It's potentially dangerous.

-- Reply to this email directly or view it on GitHub: https://github.com/AvaloniaUI/Avalonia/issues/9771#issuecomment-1968008846 You are receiving this because you commented.

Message ID: @.***> -- Sent from my Android device with K-9 Mail. Please excuse my brevity.