Pull to refresh

Анимации в WPF

Reading time11 min
Views79K

Предисловие


Приветствую вас, дорогие хабраюзеры! Сегодня я хочу вам рассказать об анимации в WPF. О ней, конечно, писали ранее на хабре, однако я постараюсь рассказать подробнее. Мой пост будет скорее больше теоретический, однако, я надеюсь, вы извлечете из него выгоду.
Анимация в WPF отличается от всего, что вы видели раньше своей наглядностью и простотой. Раньше вам приходилось вручную перерисовывать сцену по таймеру, разумеется, написав довольно большой объем кода. Теперь вы можете создать анимацию в XAML файле, не написав ни единой строчки кода на C# (или любой другой .Net язык). Разумеется, осталась возможность создавать анимацию в коде, но об этом поговорим позднее. Анимация в WPF не перерисовывает элемент, а изменяет его свойства через определенные интервалы времени (по умолчанию около 60 раз в секунду, если вы не измените в Storyboard). Это позволяет ей оставаться самой собой, например, если анимировать кнопку, то во время анимации она будет оставаться «кнопкой», т.е. на неё можно будет нажать, и производить разнообразные манипуляции над ней.

Много кода, примеров и картинок под катом…


Архитектура


Все анимации в WPF находятся в пространстве имен System.Windows.Media.Animation. Все классы анимаций начинаются с имени анимируемого типа, например DoubleAnimation анимирует свойства типа Double.

Анимации делятся на три категории:
1. Анимации с использованием линейной интерполяции (ТипСвойстваAnimation)
2. Анимации с использованием ключевых кадров (ТипСвойстваUsingKeyFrames)
3. Анимации с использованием пути (ТипСвойстваUsingPath)

Все анимации наследуются от ТипСвойстваAnimationBase. В данном пространстве имен присутствуют классы анимации для большинства BCL типов.
Для начала давайте рассмотрим простейшую анимацию в XAML:
<Button>
  <Button.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
      <BeginStoryboard>
        <Storyboard>
          <ThicknessAnimation
                From="0"
                To="200"
                Duration="0:0:5"
                Storyboard.TargetProperty
                ="Margin"/>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Button.Triggers>
</Button>


* This source code was highlighted with Source Code Highlighter.

Рассмотрим каждый класс подробнее:
• ThicknessAnimation – сама анимация.
• Storyboard – «временная шкала». О ней поговорим позднее.
• BeginStoryboard – «обертка» для Storyboard, запускающая анимацию.
• EventTrigger – триггер на событие.

После нажатия на кнопку, мы видим, что она уменьшилась:

Слева до нажатия, справа после.

Аналогичный пример на MSDN. Тег object, к сожалению, хабрапарсер не пропускает.

Анимацию можно запускать и при изменении свойства зависимости:
<Button>
  <Button.Style>
    <Style>
      <Style.Triggers>
        <Trigger Property="Button.IsPressed"
             Value="True">
          <Trigger.EnterActions>
            <BeginStoryboard>
              <Storyboard>
                <ThicknessAnimation
                  From="0" To="200"
                  Duration="0:0:5"
                  Storyboard.TargetProperty
                  ="Margin"/>
              </Storyboard>
            </BeginStoryboard>
          </Trigger.EnterActions>
        </Trigger>
      </Style.Triggers>
    </Style>
  </Button.Style>
</Button>


* This source code was highlighted with Source Code Highlighter.

Впрочем, в данном случае это даст тот же результат.

Более тонкая настройка


Свойство Storyboard.TargetProperty

Это прикрепляемое свойство задает целевое свойство для анимации. В данном случае мы применяем анимацию к свойству Margin.

Свойство Storyboard.TargetName

Это прикрепляемое свойство задает название целевого элемента в пределах видимости XAML. Используется, если мы хотим применить анимацию к другому элементу в XAML.

Свойство Storyboard.Target

Это прикрепляемое свойство задает целевой элемент, к которому мы хотим применить анимацию. Обычно используется при создании анимаций в коде. Пример на MSDN.

Свойство From

Это свойство задает начальное значение, т.е. при старте будет использоваться именно это значение. Однако это свойство можно опустить, и тогда в качестве начального значения будет использоваться текущее. Это позволяет, не дожидаясь окончания одной анимации, запустить другую, и при этом не будет «рывка». Если вы анимируете свойства Width и Height, то проверьте, чтобы значение свойства не было равно Double.NaN.

Свойство To

Это свойство задает конечное значение. Его также можно опустить, при этом будет использоваться значение, которое принимало свойство до анимаций. Например, можно сделать анимацию расширения при наведении курсора на кнопку, а при уходе курсора сделать анимацию без свойства To, то тогда при уходе кнопка «сузится» до первоначальных размеров.

Свойство By

Это свойство задает значение, на которое изменяется конечное свойство. Проще говоря, при присваивании значения свойству By, свойство To принимает значение From + By. Это сделано для облегчения использования анимаций в XAML.

Свойство By реализуется не всеми классами. В основном это свойство присутствует в числовых анимациях.

Свойство IsAdditive

Это свойство позволяет сделать значения From и To относительными. Грубо говоря, если данное свойство имеет значение True, то начальное значение будет равно «текущему значению + From», а конечное «текущему значению + To».

Свойство Duration

Это свойство задает длительность анимации. Заметьте – тип данного свойства не TimeSpan, а Duration. Почему оно тогда принимает время? Потому что есть неявное приведение к TimeSpan. “Но зачем тогда нужен Duration?” — скажите вы. Затем, что Duration также может принимать значение Duration.Automatic (эквивалентно 1 секунде) и Duration.Forever (анимация ничего не делает).

Свойство BeginTime

Это свойство задает задержку перед запуском анимации. Здесь думаю все очевидно. Пример на MSDN.

Свойство SpeedRatio

Это свойство задает скорость анимации. Его начальное значение равно 1D. При его увеличении, скорость будет соответственно увеличиваться, а при его уменьшении замедляться. Например если этому свойству присвоить 2D, то скорость увеличиться вдвое.

Свойства AccelerationRatio и DecelerationRatio

Эти свойства задают промежуток ускорения или замедления. Его значение указывается в процентном соотношении. Например, если свойству AccelerationRatio присвоить 0.5, то половину своего времени анимация будет ускоряться. Или если присвоить свойствам AccelerationRatio и DecelerationRatio 0.25, при общей длительности 4 секунды, то все произойдет так:
1. Ускорение анимации на 1 секунду
2. Постоянная скорость на 2 секунды
3. Замедление анимации на 1 секунду

Значение данных свойств не может быть больше 1 или меньше 0.

Свойство AutoReverse

Если это свойство будет равно True, то в конце анимации она будет перезапущена в обратном направлении. Т.е. если кнопка увеличивалась, то она уменьшится.

BeginTime применяется только в самом начале анимации, в обратном направлении задержки уже не будет.

Виды анимаций


Теперь рассмотрим каждый вид подробнее:

Линейная анимация

Здесь все просто – анимирует свойство из одного значения в другое, с использованием функции линейной интерполяции. Всё что мы рассматривали ранее, являлось линейной анимацией.

Анимация с использованием ключевых кадров

Эта анимация анимирует свойство по ключевым кадрам. Если вы имели дело с 3D-анимацией, то вы меня поймете.
Анимация ключевых кадров похожа на обычную анимацию, лишь с одним условием — конечных значений может быть больше 1.
Рассмотрим данный фрагмент XAML:
<Border Background="White">
  <Border.Triggers>
    <EventTrigger RoutedEvent="Border.MouseDown">
      <EventTrigger.Actions>
        <BeginStoryboard>
          <Storyboard>
            <ColorAnimationUsingKeyFrames
              Storyboard.TargetProperty
              ="(Border.Background).
              (SolidColorBrush.Color)"
>
              <LinearColorKeyFrame
                KeyTime="0:0:2"
                Value="Blue"/>
              <LinearColorKeyFrame
                KeyTime="0:0:4"
                Value="Red"/>
              <LinearColorKeyFrame
                KeyTime="0:0:6"
                Value="Green"/>
            </ColorAnimationUsingKeyFrames>
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger.Actions>
    </EventTrigger>
  </Border.Triggers>
</Border>


* This source code was highlighted with Source Code Highlighter.

ColorAnimationUsingKeyFrames – анимация цвета, используя ключевые кадры.
LinearColorKeyFrame – ключевые кадры анимации цвета с интерполяцией.
У нас есть 3 ключа. Теперь представим саму анимацию, так сказать «раскадрируем» :):
image

Существует четыре вида ключевых кадров:
1. LinearТипСвойстваKeyFrame – ключевой кадр, использующий линейную интерполяцию.
2. DiscreteТипСвойстваKeyFrame – ключевой кадр без интерполяции. Используется, если требуется резко изменить значение свойства. Единственный вид ключевых кадров для ObjectAnimationUsingKeyFrames.
3. SplineТипСвойстваKeyFrame – ключевой кадр с интерполяцией по кривой Безье. Точки кривой задаются свойством KeySpline. В остальном подобен LinearKeyFrame (тип для простоты опускаю).
4. EasingТипСвойстваKeyFrame – подобен LinearKeyFrame, только позволяет использовать функцию плавности. Свойство EasingFunction принимает объект типа EasingFunctionBase, являющейся функцией плавности. Посмотреть все доступные функции плавности можно посмотреть здесь. Галерея функций плавности на MSDN.

Каждый ключевой кадр имеет два свойства:
KeyTime – задает время, когда свойство примет целевое значение.
Value – целевое значение кадра. Зависит от типа анимации.

Анимация на основе пути

Анимация на основе пути использует объект PathGeometry для установки значения.
Рассмотрим подробнее данный фрагмент XAML:
<Window.Resources>
  <PathGeometry x:Key="path">
    <PathFigure IsClosed="True">
      <ArcSegment Point="1,0"
            Size="50,25"
            IsLargeArc="True"/>
    </PathFigure>
  </PathGeometry>
  <Storyboard x:Key="storyboard"
        Storyboard.TargetName="rect">
    <DoubleAnimationUsingPath
      Storyboard.TargetProperty="(Canvas.Left)"
      PathGeometry="{StaticResource path}"
      Source="X" Duration="0:0:5"/>
    <DoubleAnimationUsingPath
      Storyboard.TargetProperty="(Canvas.Top)"
      PathGeometry="{StaticResource path}"
      Source="Y" Duration="0:0:5"/>
  </Storyboard>
</Window.Resources>
<Canvas>
  <Path Data="{StaticResource path}"
     StrokeThickness="2" Stroke="Black"
     Canvas.Left="50"/>
  <Rectangle x:Name="rect" Stroke="Black"
        StrokeThickness="2" Width="10"
        Height="10">
    <Rectangle.RenderTransform>
      <TranslateTransform X="45" Y="-5"/>
    </Rectangle.RenderTransform>
  </Rectangle>
</Canvas>


* This source code was highlighted with Source Code Highlighter.

Все то же самое, только используется анимация на основе пути. Свойство PathGeometry задает объект PathGeometry, содержащий геометрию пути. Свойство Source задает его выходное значение, например координата X или Y.

Конечный результат

Пример на MSDN. Пример, конечно, про управление воспроизведением анимации, однако это единственный из найденных мною примеров, где используется анимация на основе пути.

Storyboard – что за зверь такой?


Вы уже заметили, что я часто использую Storyboard. Думаю, вам было очевидно, что это не анимация. Этот класс позволяет сгруппировать несколько анимаций (потому то он и наследуется от TimelineGroup). Можно сказать, что это контейнер для анимаций. Он очень удобен тем, что позволяет управлять ими всеми сразу, т.е. используя например метод Begin() запустить сразу 10 анимаций. Конечно, для одиночных анимаций он вовсе необязателен. Однако, как я писал выше, свойство BeginStoryboard.Storyboard имеет тип Storyboard, так что для его использования необходимо «оборачивать» ею анимацию.

Создание анимации в коде


Здесь всё просто, если вы разобрались с XAML. Если вы хотите применить анимацию к одному элементу достаточно создать её и вызвать у конечного элемента метод BeginAnimation().
var animation = new ThicknessAnimation();
animation.From = new Thickness(20);
animation.To = new Thickness(100);
animation.Duration = TimeSpan.FromSeconds(5);
button.BeginAnimation(MarginProperty, animation);


Также вы можете вручную задать конечный элемент и свойство для анимации используя прикрепляемые свойства Storyboard.Target и Storyboard.TargetProperty, о котором я писал выше.
var animation1 = new ThicknessAnimation();
animation1.From = new Thickness(5);
animation1.To = new Thickness(25);
animation1.Duration = TimeSpan.FromSeconds(5);
Storyboard.SetTarget(animation1, button1);
Storyboard.SetTargetProperty(animation1, new PropertyPath(MarginProperty));

var animation2 = new ThicknessAnimation();
animation2.From = new Thickness(5);
animation2.To = new Thickness(25);
animation2.Duration = TimeSpan.FromSeconds(5);
Storyboard.SetTarget(animation2, button2);
Storyboard.SetTargetProperty(animation2, new PropertyPath(MarginProperty));

var storyboard = new Storyboard();
storyboard.Children = new TimelineCollection {animation1, animation2};

storyboard.Begin();


P.S. Пользуясь моментом, хочу поздравить вас с прошедшим Новым Годом!
P.S.S. Код разметки «сжат» для повышения читаемости на мобильных девайсах (сам часто хабру со своего WP7 читаю).
Tags:
Hubs:
Total votes 15: ↑10 and ↓5+5
Comments1

Articles