DEV Community 👩‍💻👨‍💻

Lighto
Lighto

Posted on

Using the new Composition Renderer in Avalonia 11

Avalonia 11 preview was just released and it's packing new features and performance improvements.
As a performance geek, the feature I look the most into is the new "Composition" Renderer.

What's the new Composition Renderer?

The renderer is responsible for tracking, validating, and triggering the drawing of visuals, making it one of the most important components for affecting the performance and efficiency of an app.
The new renderer achieves a UWP-like composition visual tree with UI and render thread separation.
As a result, Avalonia apps are now more efficient, use less RAM and allow render-thread only animations.

Into the new animations

When a layout change occurs in an Avalonia app the renderer will re-arrange visuals to fit the new bounds.
The new Composition APIs allow Avalonia devs to create silky-smooth animations when these layout changes occur.
There are two kinds of animations, explicit and implicit - the first triggers manually from code-behind, and the later triggers as properties of a visual change.

The new composition APIs

Every Visual in Avalonia now has a CompositionVisual counterpart that holds all its compositing properties, like Size, Offset, Opacity, etc.
Just like the old animations, you can animate any of these properties.

For instance, here is an example of an explicit sliding-in animation of a control -

// Get the new composition visual
CompositionVisual compositionVisual = ElementComposition.GetElementVisual(control);
Compositor compositor = compositionVisual.Compositor;
// "Offset" is a Vector3 property, so we create a Vector3KeyFrameAnimation
Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation();
// Change the offset of the visual slightly to the left when the animation beginning
animation.InsertKeyFrame(0f, compositionVisual.Offset with {X = compositionVisual.Offset.X - 20});
// Revert the offset to the original position (0,0,0) when the animation ends
animation.InsertKeyFrame(1f, compositionVisual.Offset);
animation.Duration = TimeSpan.FromMilliseconds(300);
// Start the new animation!
compositionVisual.StartAnimation("Offset", animation);
Enter fullscreen mode Exit fullscreen mode

Here we animate the Offset property, which is a 3-vector, e.g.(x,y,z). By explicitly starting an animation with an offset of twenty vertically we can make a sliding-in animation -

Sliding-in animation

Implicit animations

Implicit animations are as you've guessed - the opposite of explicit animations, they trigger as property of visual changes.
These animations are especially useful as you only set them once and they will trigger efficiently every time a property changes.

Each CompositionVisual has an ImplicitAnimations property that contains a collection that maps between a property name, e.g. Offset to an animation or a group of animations to trigger when it changes.

As a simple example, here is how to implicitly animate the control's position change -

CompositionVisual compositionVisual = ElementComposition.GetElementVisual(control);
Compositor compositor = compositionVisual.Compositor;
Vector3KeyFrameAnimation offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
// Using the "this.FinalValue" to indicate the last value of the Offset property
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(400);
// Create a new implicit animation collection and bind the offset animation
ImplicitAnimationCollection implicitAnimationCollection = compositor.CreateImplicitAnimationCollection();
implicitAnimationCollection["Offset"] = offsetAnimation;
compositionVisual.ImplicitAnimations = implicitAnimationCollection;
Enter fullscreen mode Exit fullscreen mode

First, we create an Offset animation, and unlike previously when we explicitly set the Offset value, here we use an expression - this.FinalValue to use the value of Offset at the last frame.

Then we wire up the animation to the visual using a new ImplicitAnimationCollection that maps between "Offset" and our new offsetAnimation.
That will tell the compositor to start the offsetAnimation when the Offset property changes.
As a result, layout changes of visuals are animated -

Offset animations

How do I combine it with XAML?

To extend XAML you can use an AttachedProperty, which is a property that is declared outside of a Control, but can still affect it.

Let's say we want to create a nice zoom-in animation for all our Windows, we will start by creating a new "Extension" class for our attached property -

public class WindowAnimation : AvaloniaObject
{
    public static readonly AttachedProperty<bool> EnableScaleShowAnimationProperty =
        AvaloniaProperty.RegisterAttached<WindowBase, bool>("EnableShowScaleAnimation",
            typeof(WindowAnimation));

    static WindowAnimation()
    {
        EnableScaleShowAnimationProperty.Changed.AddClassHandler<WindowBase>(OnEnableScaleShowAnimationChanged);
    }

    private static void OnEnableScaleShowAnimationChanged(WindowBase windowBase,
        AvaloniaPropertyChangedEventArgs eventArgs)
    {
        if (eventArgs.NewValue is true)
        {
            windowBase.Opened += OnOpened;
        }
        else
        {
            windowBase.Opened -= OnOpened;
        }
    }

    private static void OnOpened(object sender, EventArgs e)
    {
        if (sender is not WindowBase windowBase || !GetEnableScaleShowAnimation(windowBase))
            return;

        // Here we explicitly animate the "Scale" property
        // The implementation is the same as `Offset` at the beginning, but just with the Scale property
        windowBase.StartWindowScaleAnimation();
    }

    public static bool GetEnableScaleShowAnimation(WindowBase element)
    {
        return element.GetValue(EnableScaleShowAnimationProperty);
    }

    public static void SetEnableScaleShowAnimation(WindowBase element, bool value)
    {
        element.SetValue(EnableScaleShowAnimationProperty, value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Then we can easily apply it to any Window using a Style in App.xaml -

<Style Selector="Window">
    <Setter Property="animations:WindowAnimation.EnableScaleShowAnimation" Value="True"/>
</Style>
Enter fullscreen mode Exit fullscreen mode

Window animation

And that's it!
Hope you found this post interesting :)

Top comments (0)

We are hiring! Do you want to be our Senior Platform Engineer? We're hiring for a Senior Platform Engineer and would love for you to apply.

Head here to learn more about who we're looking for.