Desktop UI

Анімації в Avalonia

Transitions та KeyFrame анімації в Avalonia UI — простіший підхід до анімацій порівняно з WPF

Анімації в Avalonia

Вступ

Якщо ви вже знайомі з анімаціями у WPF, то підхід Avalonia може здатися вам революційним. Там, де WPF вимагає створення Storyboard, визначення DoubleAnimation, налаштування TargetProperty та TargetName, Avalonia пропонує набагато простіший механізм — Transitions (переходи).

Філософія Avalonia полягає в тому, що анімації мають бути неявними (implicit) та декларативними. Замість того, щоб явно описувати, як саме змінюється властивість від значення A до значення B, ви просто вказуєте: "коли ця властивість зміниться, анімуй її протягом 300 мілісекунд". Це нагадує підхід CSS-transitions у веб-розробці, де зміна opacity автоматично анімується, якщо визначено transition: opacity 0.3s.

У цій статті ми розглянемо, як працюють Transitions в Avalonia, які типи переходів доступні, як використовувати KeyFrame анімації для складніших сценаріїв, і порівняємо обсяг коду з WPF. Ви побачите, що той самий ефект, який у WPF займає 15-20 рядків XAML, в Avalonia можна реалізувати в 3-5 рядків.

Важливо: Avalonia не має класу Storyboard у тому вигляді, як у WPF. Замість цього використовуються Transitions для простих анімацій та Animation з KeyFrame для складних сценаріїв.

Transitions — неявні анімації властивостей

Концепція Transitions

Transition (перехід) — це механізм, який автоматично анімує зміну властивості елемента. Ви визначаєте, яку властивість потрібно анімувати, тривалість анімації, функцію пом'якшення (easing), і Avalonia автоматично застосовує плавний перехід щоразу, коли ця властивість змінюється.

Наприклад, якщо ви встановите Opacity кнопки з 1.0 на 0.5, і визначите DoubleTransition для властивості Opacity, Avalonia автоматично створить плавну анімацію зміни прозорості. Вам не потрібно викликати Storyboard.Begin() чи писати код-behind — все відбувається декларативно.

Синтаксис Transitions

Transitions визначаються через властивість Transitions будь-якого Control. Це колекція об'єктів типу ITransition, кожен з яких відповідає за анімацію однієї властивості.

Базовий синтаксис:

<Button Content="Наведи на мене">
    <Button.Transitions>
        <Transitions>
            <DoubleTransition Property="Opacity" Duration="0:0:0.3"/>
        </Transitions>
    </Button.Transitions>
</Button>

Тепер, коли Opacity кнопки зміниться (наприклад, через стиль або код), зміна буде анімована протягом 300 мілісекунд.

Приклад: Hover-ефект з Transition

Розглянемо класичний сценарій — зміна прозорості кнопки при наведенні миші. У WPF це вимагало б EventTrigger, Storyboard, DoubleAnimation з TargetProperty. В Avalonia це виглядає так:

<Button Content="Hover Me" Opacity="1.0">
    <Button.Transitions>
        <Transitions>
            <DoubleTransition Property="Opacity" Duration="0:0:0.3" Easing="CubicEaseInOut"/>
        </Transitions>
    </Button.Transitions>
    <Button.Styles>
        <Style Selector="Button:pointerover">
            <Setter Property="Opacity" Value="0.7"/>
        </Style>
    </Button.Styles>
</Button>

Що тут відбувається:

  1. Transitions: Визначаємо, що властивість Opacity має анімуватися протягом 0.3 секунди з функцією CubicEaseInOut.
  2. Styles: Коли користувач наводить мишу (:pointerover), Opacity змінюється на 0.7.
  3. Автоматична анімація: Avalonia бачить, що Opacity змінилася, і застосовує визначений DoubleTransition — плавний перехід від 1.0 до 0.7.

Коли користувач прибирає мишу, Opacity повертається до 1.0, і анімація відбувається у зворотному напрямку.

Порада: Transitions працюють у обидва боки. Якщо властивість змінюється з A на B, анімація відбувається. Якщо потім змінюється з B на A — анімація також відбувається у зворотному напрямку.

Типи Transitions

Avalonia надає кілька вбудованих типів переходів для різних типів властивостей:

DoubleTransition

Анімує властивості типу doubleOpacity, Width, Height, FontSize, кути обертання тощо.

<DoubleTransition Property="Opacity" Duration="0:0:0.5" Easing="QuadraticEaseOut"/>

BrushTransition

Анімує зміну кольорів у Brush (наприклад, Background, Foreground). Avalonia інтерполює кольори, створюючи плавний перехід між відтінками.

<BrushTransition Property="Background" Duration="0:0:0.4"/>

Приклад використання:

<Border Background="LightBlue" CornerRadius="8" Padding="20">
    <Border.Transitions>
        <Transitions>
            <BrushTransition Property="Background" Duration="0:0:0.4"/>
        </Transitions>
    </Border.Transitions>
    <Border.Styles>
        <Style Selector="Border:pointerover">
            <Setter Property="Background" Value="LightCoral"/>
        </Style>
    </Border.Styles>
    <TextBlock Text="Наведи на мене"/>
</Border>

При наведенні фон плавно змінюється з LightBlue на LightCoral.

ThicknessTransition

Анімує властивості типу ThicknessMargin, Padding, BorderThickness.

<ThicknessTransition Property="Margin" Duration="0:0:0.3"/>

Це корисно для створення ефектів "розсування" елементів або зміни відступів.

TransformOperationsTransition

Анімує трансформації — RenderTransform. Це найпотужніший тип переходу, оскільки дозволяє анімувати обертання, масштабування, зсув одночасно.

<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.5" Easing="BackEaseOut"/>

Приклад — масштабування кнопки при наведенні:

<Button Content="Збільш мене" RenderTransformOrigin="0.5, 0.5">
    <Button.Transitions>
        <Transitions>
            <TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.3"/>
        </Transitions>
    </Button.Transitions>
    <Button.Styles>
        <Style Selector="Button:pointerover">
            <Setter Property="RenderTransform" Value="scale(1.1)"/>
        </Style>
    </Button.Styles>
</Button>

Тут використовується CSS-подібний синтаксис scale(1.1) для масштабування. Avalonia підтримує функції translate(), rotate(), scale(), skew() у властивості RenderTransform.

Словник:
  • Transition — механізм автоматичної анімації зміни властивості.
  • Easing — функція пом'якшення, яка визначає темп анімації (лінійний, прискорення, уповільнення тощо).
  • Pseudo-class — селектор стану елемента (:pointerover, :pressed, :disabled), аналог WPF Triggers.

Easing Functions у Avalonia

Avalonia підтримує ті ж функції пом'якшення (easing), що й WPF, але з дещо іншими назвами. Ось найпоширеніші:

Avalonia EasingОписАналог у WPF
LinearEasingЛінійна анімація без прискоренняLinearEase
QuadraticEaseInПрискорення на початкуQuadraticEase (EaseIn)
QuadraticEaseOutУповільнення в кінціQuadraticEase (EaseOut)
CubicEaseInOutПрискорення на початку, уповільнення в кінціCubicEase (EaseInOut)
BackEaseOut"Відскок" в кінці анімаціїBackEase (EaseOut)
BounceEaseOutЕфект "стрибка" в кінціBounceEase (EaseOut)
ElasticEaseOutЕластичний ефект (як пружина)ElasticEase (EaseOut)

Приклад використання:

<DoubleTransition Property="Opacity" Duration="0:0:0.5" Easing="BackEaseOut"/>

Функції easing роблять анімації більш природними та приємними для ока. Наприклад, BackEaseOut створює ефект "перельоту" — елемент трохи виходить за межі кінцевого значення, а потім повертається назад.


CSS-подібний підхід: Pseudo-classes та Styles

Одна з найбільших переваг Avalonia — це інтеграція анімацій зі стилями через pseudo-classes (псевдокласи). Це селектори стану елемента, які автоматично застосовуються при певних умовах.

Основні Pseudo-classes

  • :pointerover — курсор миші над елементом (аналог WPF IsMouseOver).
  • :pressed — елемент натиснутий (аналог IsPressed).
  • :disabled — елемент вимкнений (аналог IsEnabled=False).
  • :focus — елемент має фокус клавіатури.
  • :checked — для CheckBox та RadioButton (аналог IsChecked=True).

Приклад: Анімація кнопки з кількома станами

<Button Content="Інтерактивна кнопка" Opacity="1.0" RenderTransformOrigin="0.5, 0.5">
    <Button.Transitions>
        <Transitions>
            <DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
            <TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2"/>
        </Transitions>
    </Button.Transitions>
    <Button.Styles>
        <Style Selector="Button:pointerover">
            <Setter Property="Opacity" Value="0.8"/>
            <Setter Property="RenderTransform" Value="scale(1.05)"/>
        </Style>
        <Style Selector="Button:pressed">
            <Setter Property="Opacity" Value="0.6"/>
            <Setter Property="RenderTransform" Value="scale(0.95)"/>
        </Style>
    </Button.Styles>
</Button>

Що відбувається:

  1. Hover (:pointerover): Прозорість зменшується до 0.8, кнопка збільшується до 105%.
  2. Pressed (:pressed): Прозорість зменшується до 0.6, кнопка зменшується до 95% (ефект "натискання").
  3. Transitions: Обидві властивості (Opacity та RenderTransform) анімуються плавно.

Це всього 15 рядків XAML. У WPF аналогічний ефект вимагав би ControlTemplate з VisualStateManager або кілька EventTrigger з Storyboard — близько 40-50 рядків коду.

Порада: Використовуйте :pointerover замість :mouseover — це працює як з мишею, так і з сенсорними екранами.

KeyFrame Animations — складні сценарії

Хоча Transitions чудово підходять для простих анімацій (зміна однієї властивості з A на B), іноді потрібні складніші сценарії — наприклад, анімація з кількома етапами, зміна кількох властивостей одночасно, або анімація, яка запускається програмно (не через зміну властивості).

Для таких випадків Avalonia надає KeyFrame Animations — механізм, схожий на WPF Storyboard, але з більш зручним API.

Структура KeyFrame Animation

KeyFrame анімація складається з:

  1. Animation — контейнер, який визначає тривалість, режим повторення, затримку.
  2. KeyFrame — ключовий кадр, який визначає стан властивостей у певний момент часу (через Cue).
  3. Setter — встановлення значення властивості у цьому кадрі.

Базовий синтаксис:

<Animation Duration="0:0:2" IterationCount="Infinite">
    <KeyFrame Cue="0%">
        <Setter Property="Opacity" Value="1.0"/>
    </KeyFrame>
    <KeyFrame Cue="50%">
        <Setter Property="Opacity" Value="0.3"/>
    </KeyFrame>
    <KeyFrame Cue="100%">
        <Setter Property="Opacity" Value="1.0"/>
    </KeyFrame>
</Animation>

Що тут відбувається:

  • Duration: Загальна тривалість анімації — 2 секунди.
  • IterationCount: Кількість повторень. Infinite означає нескінченне повторення.
  • Cue: Відсоток часу анімації. 0% — початок, 50% — середина, 100% — кінець.
  • Setter: Встановлює значення властивості у цьому кадрі.

Результат: Opacity плавно змінюється від 1.0 до 0.3 (перша половина анімації), потім від 0.3 до 1.0 (друга половина). Анімація повторюється нескінченно — ефект "пульсації".

Приклад: Обертання елемента

Створимо анімацію обертання іконки на 360 градусів:

<Border Width="100" Height="100" Background="DodgerBlue" CornerRadius="50"
        RenderTransformOrigin="0.5, 0.5">
    <Border.Styles>
        <Style Selector="Border">
            <Style.Animations>
                <Animation Duration="0:0:2" IterationCount="Infinite">
                    <KeyFrame Cue="0%">
                        <Setter Property="RenderTransform" Value="rotate(0deg)"/>
                    </KeyFrame>
                    <KeyFrame Cue="100%">
                        <Setter Property="RenderTransform" Value="rotate(360deg)"/>
                    </KeyFrame>
                </Animation>
            </Style.Animations>
        </Style>
    </Border.Styles>
    <TextBlock Text="🔄" FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>

Важливі моменти:

  • Style.Animations: KeyFrame анімації визначаються всередині стилю. Це означає, що анімація застосовується автоматично при завантаженні елемента.
  • RenderTransformOrigin: Встановлено на 0.5, 0.5 (центр елемента), щоб обертання відбувалося навколо центру, а не лівого верхнього кута.
  • rotate(0deg)rotate(360deg): CSS-подібний синтаксис для обертання.

Приклад: Складна анімація з кількома властивостями

Створимо анімацію, яка одночасно змінює розмір, прозорість та колір фону:

<Border Width="100" Height="100" Background="LightGreen" CornerRadius="8"
        RenderTransformOrigin="0.5, 0.5" Opacity="1.0">
    <Border.Styles>
        <Style Selector="Border">
            <Style.Animations>
                <Animation Duration="0:0:3" IterationCount="Infinite">
                    <KeyFrame Cue="0%">
                        <Setter Property="Opacity" Value="1.0"/>
                        <Setter Property="RenderTransform" Value="scale(1.0)"/>
                        <Setter Property="Background" Value="LightGreen"/>
                    </KeyFrame>
                    <KeyFrame Cue="50%">
                        <Setter Property="Opacity" Value="0.5"/>
                        <Setter Property="RenderTransform" Value="scale(1.3)"/>
                        <Setter Property="Background" Value="LightCoral"/>
                    </KeyFrame>
                    <KeyFrame Cue="100%">
                        <Setter Property="Opacity" Value="1.0"/>
                        <Setter Property="RenderTransform" Value="scale(1.0)"/>
                        <Setter Property="Background" Value="LightGreen"/>
                    </KeyFrame>
                </Animation>
            </Style.Animations>
        </Style>
    </Border.Styles>
    <TextBlock Text="✨" FontSize="36" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>

Ця анімація створює ефект "дихання" — елемент збільшується, стає напівпрозорим та змінює колір, потім повертається до початкового стану.

Важливо: KeyFrame анімації у Avalonia не потребують TargetName або TargetProperty у форматі (Button.Opacity), як у WPF. Просто вказуєте Property="Opacity" — набагато простіше!

Програмне керування анімаціями

На відміну від Transitions, які запускаються автоматично, KeyFrame анімації можна запускати програмно через код:

// У ViewModel або Code-Behind
var animation = new Animation
{
    Duration = TimeSpan.FromSeconds(1),
    Children =
    {
        new KeyFrame
        {
            Cue = new Cue(0),
            Setters = { new Setter(OpacityProperty, 0.0) }
        },
        new KeyFrame
        {
            Cue = new Cue(1),
            Setters = { new Setter(OpacityProperty, 1.0) }
        }
    }
};

await animation.RunAsync(myControl);

Метод RunAsync() запускає анімацію та повертає Task, який завершується після закінчення анімації. Це зручно для послідовних анімацій або анімацій, які залежать від бізнес-логіки.


Порівняння WPF Storyboard vs Avalonia Transitions

Давайте порівняємо обсяг коду для однієї й тієї ж анімації — зміна прозорості кнопки при наведенні миші.

WPF Storyboard (verbose, explicit)

<Button Content="Hover Me">
    <Button.Style>
        <Style TargetType="Button">
            <Style.Triggers>
                <EventTrigger RoutedEvent="MouseEnter">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                           To="0.7"
                                           Duration="0:0:0.3">
                                <DoubleAnimation.EasingFunction>
                                    <CubicEase EasingMode="EaseInOut"/>
                                </DoubleAnimation.EasingFunction>
                            </DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
                <EventTrigger RoutedEvent="MouseLeave">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                           To="1.0"
                                           Duration="0:0:0.3">
                                <DoubleAnimation.EasingFunction>
                                    <CubicEase EasingMode="EaseInOut"/>
                                </DoubleAnimation.EasingFunction>
                            </DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

Рядків коду: ~25

Avalonia Transitions (concise, implicit)

<Button Content="Hover Me" Opacity="1.0">
    <Button.Transitions>
        <Transitions>
            <DoubleTransition Property="Opacity" Duration="0:0:0.3" Easing="CubicEaseInOut"/>
        </Transitions>
    </Button.Transitions>
    <Button.Styles>
        <Style Selector="Button:pointerover">
            <Setter Property="Opacity" Value="0.7"/>
        </Style>
    </Button.Styles>
</Button>

Рядків коду: ~10

Порівняльна таблиця

АспектWPF StoryboardAvalonia Transitions
Обсяг коду25+ рядків10 рядків
ЧитабельністьСкладна вкладеністьПлоска структура
ДвосторонністьПотрібні 2 EventTrigger (Enter/Leave)Автоматично працює в обидва боки
EasingОкремий елемент <EasingFunction>Атрибут Easing="..."
TargetPropertyРядок "Opacity" з можливістю помилкиСтрого типізована властивість
ЗапускЯвний через BeginStoryboardНеявний при зміні властивості

Переваги Avalonia Transitions

  • Менше коду: У 2-3 рази менше XAML для тієї ж анімації.
  • Простіше: Не потрібно розуміти EventTrigger, BeginStoryboard, TargetProperty.
  • Декларативність: Описуєте "що" анімувати, а не "як" це робити.
  • CSS-подібність: Знайомий підхід для веб-розробників.
  • Автоматична двосторонність: Не потрібно писати окремі анімації для "вперед" та "назад".

Переваги WPF Storyboard

  • Більше контролю: Можна анімувати вкладені властивості (наприклад, (Button.RenderTransform).(ScaleTransform.ScaleX)).
  • Складні сценарії: ParallelTimeline, BeginTime, RepeatBehavior="Forever" з точним контролем.
  • Зрілість: Більше документації, прикладів, Stack Overflow відповідей.

Практичні поради та Best Practices

1. Використовуйте Transitions для простих анімацій

Якщо вам потрібно анімувати зміну однієї властивості (прозорість, колір, розмір), завжди використовуйте Transitions. Це простіше, коротше та зрозуміліше.

<!-- ✅ Добре: Transition для простої анімації -->
<Button.Transitions>
    <Transitions>
        <DoubleTransition Property="Opacity" Duration="0:0:0.3"/>
    </Transitions>
</Button.Transitions>

2. Використовуйте KeyFrame для складних сценаріїв

Якщо анімація має кілька етапів, змінює багато властивостей одночасно, або потребує програмного запуску — використовуйте KeyFrame Animations.

<!-- ✅ Добре: KeyFrame для складної анімації -->
<Style.Animations>
    <Animation Duration="0:0:2">
        <KeyFrame Cue="0%">
            <Setter Property="Opacity" Value="0"/>
            <Setter Property="RenderTransform" Value="translateY(-20px)"/>
        </KeyFrame>
        <KeyFrame Cue="100%">
            <Setter Property="Opacity" Value="1"/>
            <Setter Property="RenderTransform" Value="translateY(0)"/>
        </KeyFrame>
    </Animation>
</Style.Animations>

3. Не змішуйте Transitions та KeyFrame для однієї властивості

Якщо ви визначили DoubleTransition для Opacity, і одночасно маєте KeyFrame Animation, яка також змінює Opacity, результат може бути непередбачуваним. Avalonia спробує застосувати обидві анімації, що призведе до конфліктів.

<!-- ❌ Погано: Конфлікт між Transition та KeyFrame -->
<Button.Transitions>
    <Transitions>
        <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
    </Transitions>
</Button.Transitions>
<Button.Styles>
    <Style Selector="Button">
        <Style.Animations>
            <Animation Duration="0:0:1">
                <KeyFrame Cue="0%">
                    <Setter Property="Opacity" Value="0"/>
                </KeyFrame>
                <KeyFrame Cue="100%">
                    <Setter Property="Opacity" Value="1"/>
                </KeyFrame>
            </Animation>
        </Style.Animations>
    </Style>
</Button.Styles>

Рішення: Використовуйте або Transition, або KeyFrame Animation для однієї властивості, але не обидва одночасно.

4. Оптимізуйте тривалість анімацій

Занадто швидкі анімації (< 100 мс) можуть бути непомітними. Занадто повільні (> 500 мс) можуть дратувати користувача. Оптимальний діапазон для більшості UI-анімацій — 200-400 мс.

Тип анімаціїРекомендована тривалість
Hover-ефекти200-300 мс
Модальні вікна (поява/зникнення)300-400 мс
Переходи між сторінками400-500 мс
Мікроанімації (іконки, індикатори)150-250 мс

5. Використовуйте RenderTransformOrigin для обертання та масштабування

Якщо ви анімуєте RenderTransform (обертання, масштабування), завжди встановлюйте RenderTransformOrigin, щоб контролювати точку трансформації.

<!-- ✅ Добре: Обертання навколо центру -->
<Button RenderTransformOrigin="0.5, 0.5">
    <Button.Styles>
        <Style Selector="Button:pointerover">
            <Setter Property="RenderTransform" Value="rotate(5deg)"/>
        </Style>
    </Button.Styles>
</Button>

Без RenderTransformOrigin обертання відбуватиметься навколо лівого верхнього кута (0, 0), що виглядає неприродно.

Увага: На відміну від WPF, де RenderTransformOrigin за замовчуванням 0, 0, в Avalonia це також 0, 0. Завжди явно встановлюйте 0.5, 0.5 для центрованих трансформацій.

Реальний приклад: Портування WPF анімації на Avalonia

Давайте візьмемо складну анімацію з WPF (з попередньої статті про WPF анімації) та портуємо її на Avalonia, щоб побачити різницю в обсязі коду.

Сценарій: Анімація бічної панелі (Sidebar)

У WPF ми створювали анімацію, яка висуває бічну панель з лівого краю екрану. Панель спочатку має Width="0", а при натисканні кнопки розширюється до Width="250".

WPF версія (Storyboard)

<!-- WPF: ~30 рядків коду -->
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    
    <Border x:Name="Sidebar" Grid.Column="0" Width="0" Background="LightGray">
        <StackPanel Margin="10">
            <TextBlock Text="Меню" FontWeight="Bold"/>
            <Button Content="Пункт 1" Margin="0,5"/>
            <Button Content="Пункт 2" Margin="0,5"/>
        </StackPanel>
    </Border>
    
    <Button Grid.Column="1" Content="Показати меню" Click="ToggleSidebar_Click"/>
</Grid>

<!-- Code-Behind -->
<Window.Resources>
    <Storyboard x:Key="ShowSidebar">
        <DoubleAnimation Storyboard.TargetName="Sidebar"
                       Storyboard.TargetProperty="Width"
                       To="250"
                       Duration="0:0:0.4">
            <DoubleAnimation.EasingFunction>
                <CubicEase EasingMode="EaseOut"/>
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
    </Storyboard>
    <Storyboard x:Key="HideSidebar">
        <DoubleAnimation Storyboard.TargetName="Sidebar"
                       Storyboard.TargetProperty="Width"
                       To="0"
                       Duration="0:0:0.4">
            <DoubleAnimation.EasingFunction>
                <CubicEase EasingMode="EaseOut"/>
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
    </Storyboard>
</Window.Resources>
// Code-Behind: ~10 рядків
private bool isSidebarVisible = false;

private void ToggleSidebar_Click(object sender, RoutedEventArgs e)
{
    var storyboard = isSidebarVisible 
        ? (Storyboard)FindResource("HideSidebar") 
        : (Storyboard)FindResource("ShowSidebar");
    
    storyboard.Begin();
    isSidebarVisible = !isSidebarVisible;
}

Загалом: ~40 рядків XAML + Code-Behind.

Avalonia версія (Transitions)

<!-- Avalonia: ~15 рядків коду -->
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    
    <Border x:Name="Sidebar" Grid.Column="0" Width="0" Background="LightGray">
        <Border.Transitions>
            <Transitions>
                <DoubleTransition Property="Width" Duration="0:0:0.4" Easing="CubicEaseOut"/>
            </Transitions>
        </Border.Transitions>
        <StackPanel Margin="10">
            <TextBlock Text="Меню" FontWeight="Bold"/>
            <Button Content="Пункт 1" Margin="0,5"/>
            <Button Content="Пункт 2" Margin="0,5"/>
        </StackPanel>
    </Border>
    
    <Button Grid.Column="1" Content="Показати меню" Click="ToggleSidebar_Click"/>
</Grid>
// Code-Behind: ~5 рядків
private bool isSidebarVisible = false;

private void ToggleSidebar_Click(object sender, RoutedEventArgs e)
{
    Sidebar.Width = isSidebarVisible ? 0 : 250;
    isSidebarVisible = !isSidebarVisible;
}

Загалом: ~20 рядків XAML + Code-Behind.

Порівняння

АспектWPFAvaloniaРізниця
Рядків XAML3015-50%
Рядків C#105-50%
Storyboard ресурси2 (Show/Hide)0-100%
FindResource()ТакНіПростіше
ЧитабельністьСередняВисокаЗрозуміліше

Avalonia версія вдвічі коротша та набагато зрозуміліша. Не потрібно створювати окремі Storyboard для "показати" та "сховати" — Transition автоматично працює в обидва боки.

Порада: Якщо ви портуєте WPF додаток на Avalonia, почніть з заміни Storyboard на Transitions. Це одразу зменшить обсяг коду на 30-50% та покращить читабельність.

Практичні завдання

Рівень 1: Hover-анімація кнопки через Transition

Мета: Створити кнопку, яка при наведенні миші плавно змінює колір фону та збільшується на 10%.

Вимоги:

  • Використайте BrushTransition для анімації Background.
  • Використайте TransformOperationsTransition для анімації RenderTransform.
  • Тривалість анімації — 300 мс.
  • Easing функція — CubicEaseInOut.

Підказка:

<Button Content="Наведи на мене" Background="DodgerBlue" RenderTransformOrigin="0.5, 0.5">
    <Button.Transitions>
        <Transitions>
            <!-- Додайте BrushTransition та TransformOperationsTransition -->
        </Transitions>
    </Button.Transitions>
    <Button.Styles>
        <Style Selector="Button:pointerover">
            <!-- Встановіть Background="LightCoral" та RenderTransform="scale(1.1)" -->
        </Style>
    </Button.Styles>
</Button>

Очікуваний результат: При наведенні миші кнопка плавно змінює колір з синього на коралловий та збільшується на 10%. При прибиранні миші — повертається до початкового стану.


Рівень 2: KeyFrame анімація обертання

Мета: Створити іконку завантаження (spinner), яка безперервно обертається на 360 градусів.

Вимоги:

  • Використайте KeyFrame Animation з двома кадрами: 0% (0 градусів) та 100% (360 градусів).
  • Анімація має повторюватися нескінченно (IterationCount="Infinite").
  • Тривалість одного обертання — 1.5 секунди.
  • Використайте LinearEasing для рівномірного обертання.

Підказка:

<Border Width="50" Height="50" Background="Transparent" RenderTransformOrigin="0.5, 0.5">
    <Border.Styles>
        <Style Selector="Border">
            <Style.Animations>
                <Animation Duration="0:0:1.5" IterationCount="Infinite">
                    <KeyFrame Cue="0%">
                        <Setter Property="RenderTransform" Value="rotate(0deg)"/>
                    </KeyFrame>
                    <KeyFrame Cue="100%">
                        <!-- Додайте rotate(360deg) -->
                    </KeyFrame>
                </Animation>
            </Style.Animations>
        </Style>
    </Border.Styles>
    <Path Data="M 25,5 A 20,20 0 1,1 24.9,5" Stroke="DodgerBlue" StrokeThickness="4"/>
</Border>

Очікуваний результат: Іконка безперервно обертається по годинниковій стрілці.


Рівень 3: Портувати sidebar-анімацію з WPF на Avalonia Transitions

Мета: Взяти WPF приклад з бічною панеллю (sidebar) та повністю портувати його на Avalonia з використанням Transitions.

Вимоги:

  • Створіть Grid з двома колонками: Auto (для sidebar) та * (для основного контенту).
  • Sidebar має початкову ширину 0 та фон LightGray.
  • При натисканні кнопки "Показати меню" sidebar розширюється до 250 пікселів.
  • При повторному натисканні — згортається назад до 0.
  • Використайте DoubleTransition для анімації Width.
  • Тривалість анімації — 400 мс, easing — CubicEaseOut.

Підказка:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    
    <Border x:Name="Sidebar" Grid.Column="0" Width="0" Background="LightGray">
        <Border.Transitions>
            <!-- Додайте DoubleTransition для Width -->
        </Border.Transitions>
        <StackPanel Margin="10">
            <TextBlock Text="Меню" FontWeight="Bold"/>
            <Button Content="Пункт 1" Margin="0,5"/>
            <Button Content="Пункт 2" Margin="0,5"/>
            <Button Content="Пункт 3" Margin="0,5"/>
        </StackPanel>
    </Border>
    
    <StackPanel Grid.Column="1" Margin="20">
        <Button Content="Показати меню" Click="ToggleSidebar_Click"/>
        <TextBlock Text="Основний контент" Margin="0,20"/>
    </StackPanel>
</Grid>
// Code-Behind
private bool isSidebarVisible = false;

private void ToggleSidebar_Click(object sender, RoutedEventArgs e)
{
    // Додайте логіку перемикання Width між 0 та 250
}

Очікуваний результат: При натисканні кнопки sidebar плавно висувається з лівого краю. При повторному натисканні — плавно ховається.

Бонус: Додайте анімацію Opacity для sidebar (від 0 до 1), щоб панель не тільки висувалася, але й плавно з'являлася.


Резюме

Avalonia пропонує революційно простіший підхід до анімацій порівняно з WPF. Замість складних Storyboard з EventTrigger та BeginStoryboard, ви використовуєте:

  1. Transitions — для простих анімацій властивостей. Визначаєте, яку властивість анімувати, тривалість та easing — все інше відбувається автоматично.
  2. KeyFrame Animations — для складних сценаріїв з кількома етапами. CSS-подібний синтаксис з Cue (відсотки часу) замість BeginTime та Duration.
  3. Pseudo-classes — для інтеграції анімацій зі стилями. :pointerover, :pressed, :focus автоматично застосовують стилі, які анімуються через Transitions.

Ключові переваги Avalonia:

  • Менше коду: У 2-3 рази менше XAML для тієї ж анімації.
  • Простіше: Не потрібно розуміти TargetProperty, TargetName, BeginStoryboard.
  • Декларативність: Описуєте "що" анімувати, а не "як".
  • Автоматична двосторонність: Transitions працюють в обидва боки без додаткового коду.

Якщо ви портуєте WPF додаток на Avalonia, заміна Storyboard на Transitions — це перше, що варто зробити. Це одразу зменшить обсяг коду та покращить читабельність.


Словник термінів

Основні терміни:
  • Transition — механізм автоматичної анімації зміни властивості. Визначається через <Button.Transitions>.
  • KeyFrame Animation — анімація з кількома ключовими кадрами, кожен з яких визначає стан властивостей у певний момент часу.
  • Cue — відсоток часу анімації (0% — початок, 100% — кінець). Використовується у KeyFrame.
  • Pseudo-class — селектор стану елемента (:pointerover, :pressed, :focus). Аналог WPF Triggers.
  • Easing Function — функція пом'якшення, яка визначає темп анімації (лінійний, прискорення, уповільнення, відскок тощо).
  • RenderTransformOrigin — точка, навколо якої відбувається трансформація (обертання, масштабування). Значення 0.5, 0.5 означає центр елемента.
  • IterationCount — кількість повторень анімації. Infinite означає нескінченне повторення.
  • TransformOperationsTransition — тип переходу для анімації трансформацій (RenderTransform).

Додаткові ресурси

Avalonia Animations Documentation

Офіційна документація Avalonia про Transitions та KeyFrame анімації.

Avalonia Animations Tutorial

Відеоуроки на YouTube про створення анімацій в Avalonia.

Avalonia Samples — Animations

Репозиторій з прикладами коду Avalonia, включаючи розділ про анімації.

Easing Functions Visualizer

Інтерактивна візуалізація функцій пом'якшення (easing functions) для вибору оптимального ефекту.

Наступна стаття: 2D/3D графіка та мультимедіа — використання графічних примітивів, Path, Brushes, Geometries та MediaElement у WPF.