Перекрывающиеся и синхронизированные анимации

35

Перекрывающиеся анимации

Раскадровка предоставляет возможность изменять способ работы с перекрывающимися анимациями — другими словами, когда вторая анимация применяется к свойству, которое уже анимируется. Это делается через свойство BeginStoryboard.НаndoffBehavior.

Обычно, когда анимации перекрываются, то вторая переопределяет первую немедленно. Такое поведение называется снимок и замена (snapshot-and-replace) и представляется значением SnapshotAndReplace из перечисления HandoffBehavior. Когда стартует вторая анимация, создается снимок свойства в текущем состоянии (полученном в результате первой анимации), первая анимация останавливается и заменяется новой анимацией.

Единственным альтернативным значением перечисления HandoffBehavior является Compose, которое приводит к объединению второй анимации с временной шкалой первой анимации. Например, рассмотрим измененную версию примера ListBox, использующую HandoffBehavior.Compose при уменьшении элемента списка:

<EventTrigger RoutedEvent="ListBoxItem.MouseLeave">
   <EventTrigger.Actions>
      <BeginStoryboard HandoffBehavior="Compose">
         <Storyboard>
            <DoubleAnimation Storyboard.TargetProperty="FontSize"
                BeginTime="0:0:0.5" Duration="0:0:0.2"></DoubleAnimation>
         </Storyboard>
       </BeginStoryboard>
   </EventTrigger.Actions>
</EventTrigger>

Если теперь переместить курсор мыши на ListBoxItem и обратно, можно заметить другое поведение. Убрав курсор мыши с элемента, вы увидите, что он продолжит увеличиваться до тех пор, пока не достигнет начала полусекундной задержки. Затем вторая анимация уменьшит элемент. Если не указано поведение Compose, то элемент будет просто ожидать, зафиксированный в своем текущем размере, в течение 0,5 секунды, пока не начнется вторая анимация.

Использование перечисления HandoffBehavior для композиции анимаций требует дополнительных накладных расходов. Это связано с тем, что часы, используемые для запуска исходной анимации, не могут быть остановлены при запуске второй анимации. Вместо этого она остается активной до тех пор, пока сборщик мусора не удалит ListBoxItem, или же пока не будет применена новая анимация на том же свойстве.

Если производительность становится проблемой, команда разработчиков WPF рекомендует вручную освобождать часы анимаций, как только они будут завершены (а не ожидать, пока сборщик мусора найдет их). Для этого понадобится обработать событие типа Storyboard.Completed. Затем необходимо вызвать BeginAnimation() для элемента, анимация которого только что завершилась, указав соответствующее свойство и null-ссылку на месте анимации.

Синхронизированные анимации

Класс Storyboard непрямо унаследован от TimeLineGroup, что дает ему возможность поддерживать более одной анимации. Лучше всего то, что эти анимации управляются как единая группа — в том смысле, что запускаются одновременно.

Для примера рассмотрим приведенную ниже раскадровку. Она запускает две анимации: одну, работающую со свойством Width кнопки, и вторую, имеющую дело со свойством Height. Поскольку анимации сгруппированы в одной раскадровке, они увеличивают размеры кнопки в унисон, что дает более синхронизированный эффект, чем просто многократный вызов BeginAnimation() в коде:


<EventTrigger RoutedEvent="Button.Click">
   <EventTrigger.Actions>
      <BeginStoryboard>
         <Storyboard>
           <DoubleAnimation Storyboard.TargetProperty="Width"
              To="300" Duration="0:0:5"></DoubleAnimation>
           <DoubleAnimation Storyboard.TargetProperty="Height"
              To="300" Duration="0:0:5"></DoubleAnimation>
         </Storyboard>
       </BeginStoryboard>
   </EventTrigger.Actions>
</EventTrigger>

В этом примере обе анимации имеют одинаковую длительность, хотя это требование не обязательно. Единственное, что следует принимать во внимание для анимаций, завершающихся в разное время — это их свойство FillBehavior. Если FillBehavior анимации установлено в HoldEnd, она удерживает значение анимируемого свойства до тех пор, пока не завершатся все анимации, определенные в раскадровке. Если свойство FillBehavior раскадровки равно Stop, то финальное анимированное значение удерживается независимо (до тех пор, пока новая анимация не заменит его или пока анимация не будет удалена вручную).

Здесь становится ясно, в чем практическая польза свойств анимации. Например, свойство SpeedRatio можно использовать для того, чтобы заставить одну анимацию в раскадровке выполняться быстрее остальных. С помощью свойства BeginTime можно сдвинуть одну анимацию относительно другой, чтобы она запускалась в определенный момент времени.

Пройди тесты
Лучший чат для C# программистов