Анимация в WPF и Blend SDK

в 15:48, , рубрики: .net, blend sdk, Microsoft.Expression.Interactions, System.Windows.Interactivity, wpf

Всем добрый день!

В этой статье я опишу простой способ запуска анимации с помощью инструмента Blend SDK от Microsoft.

С анимациями в WPF дела обстоят не очень легко и их стараются избежать по нескольким причинам. Первая — их тяжело запускать и сложно останавливать. Вторая — они не очень быстрые.

Разберемся с запуском — что же такого «сложного». Нарисуем простой ItemsControl, внутри которого есть Canvas и размещаются маленькие шарики.

<ItemsControl ItemsSource="{Binding Bubbles}">

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas IsItemsHost="True" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Canvas.Left" Value="{Binding X}" />
            <Setter Property="Canvas.Top"  Value="{Binding Y}" />
        </Style>
    </ItemsControl.ItemContainerStyle>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Ellipse
                Width="10"
                Height="10"
                Fill="#FF616AB6" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>

</ItemsControl>

Демонстрация результата:

image

Теперь надо сделать их анимириванными, для этого перенесем наши вычисления позиции из Canvas.Left и Canvas.Top в анимацию, которая будет задавать Canvas.Left и Canvas.Top элемента. Разместим её в ресурсах каждого Ellipse'а.

<Ellipse.Resources>

    <Storyboard x:Key="yanimation">
        <DoubleAnimation
            Storyboard.Target="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentPresenter}}"
            Storyboard.TargetProperty="(Canvas.Top)"
            Duration="0:0:1" To="{Binding Y}" />
    </Storyboard>

    <Storyboard x:Key="xanimation">
        <DoubleAnimation
            Storyboard.Target="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentPresenter}}"
            Storyboard.TargetProperty="(Canvas.Left)"
            Duration="0:0:1" To="{Binding X}" />
    </Storyboard>

</Ellipse.Resources>

Но теперь вопрос — когда запускать анимации? Однозначно нам нужно запускать при появлении элемента, то есть добавим:

<Ellipse.Triggers>
    <EventTrigger RoutedEvent="Ellipse.Loaded">
        <BeginStoryboard Storyboard="{StaticResource xanimation}" />
    </EventTrigger>
    <EventTrigger RoutedEvent="Ellipse.Loaded">
        <BeginStoryboard Storyboard="{StaticResource yanimation}" />
    </EventTrigger>
</Ellipse.Triggers>

Но что теперь? По хорошему можно забиндить запуск анимации на самих себя же, добавив в Binding каждого вычисления To по NotifyOnTargetUpdated=True. Тогда можно написать

<Ellipse.Triggers>
    <EventTrigger RoutedEvent="Binding.TargetUpdated">
        <BeginStoryboard Storyboard="{StaticResource xanimation}" />
    </EventTrigger>
    <EventTrigger RoutedEvent="Binding.TargetUpdated">
        <BeginStoryboard Storyboard="{StaticResource yanimation}" />
    </EventTrigger>
</Ellipse.Triggers>

И всё работает. Но не всё так просто. В живом проекте такой подход работать отказался — скорее всего из-за зацикливания вызовов. Также где угодно можно написать NotifyOnTargetUpdated=True и анимации будут вызываться лишний раз. Нам же нужно, чтобы анимации запускались только по изменении свойств X и Y. Тут то и приходит нам на выручку Blend SDK с его библиотеками Microsoft.Expression.Interactions и System.Windows.Interactivity.

Добавляем их в проект (я добавлял через nuget). И прописываем нужные нам xmlns:

xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

Теперь можно внутри каждого Ellipse'а написать:

<i:Interaction.Triggers> 
    <ei:PropertyChangedTrigger Binding="{Binding X}">
        <ei:ControlStoryboardAction Storyboard="{StaticResource xanimation}" />
    </ei:PropertyChangedTrigger>
    <ei:PropertyChangedTrigger Binding="{Binding Y}">
        <ei:ControlStoryboardAction Storyboard="{StaticResource yanimation}" />
    </ei:PropertyChangedTrigger>
</i:Interaction.Triggers>

И анимации запустились:

image

Jobs done!

Мы не запускаем их лишний раз и они работают так как нам нужно. К тому же Blend SDK содержит множество других простых и, при этом, невероятно полезных вещей — советую ознакомиться:

Microsoft.Expression.Interactions documentation
System.Windows.Interactivity documentation

И, возвращаясь к началу статьи, хочу обратить внимание на второй пункт — то, что анимации очень медленные. Да — они такие, что с этим делать я не знаю, но тем не менее буду стараться выяснить.

Всем спасибо за внимание!

Ссылка на проект на github'е.

Автор: Kiel

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js