Поделиться через


Советы и рекомендации по анимации

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

Общие проблемы

Пытаясь анимировать положение полосы прокрутки или ползунка, он зависает.

Если вы анимируете положение полосы прокрутки или ползунка с помощью анимации с FillBehaviorHoldEnd (значение по умолчанию), пользователь больше не сможет переместить полосу прокрутки или ползунок. Это связано с тем, что, хотя анимация закончилась, она по-прежнему переопределяет базовое значение целевого свойства. Чтобы предотвратить переопределение текущего значения свойства анимацией, удалите её или установите параметр FillBehavior на Stop. Дополнительные сведения и пример см. в разделе Установите свойство после его анимации с помощью сториборда.

Анимирование результата анимации не влияет

Не удается анимировать объект, который является выходными данными другой анимации. Например, если вы используете ObjectAnimationUsingKeyFrames для анимации Fill из Rectangle от RadialGradientBrush к SolidColorBrush, вы не можете анимировать какие-либо свойства RadialGradientBrush или SolidColorBrush.

Не удается изменить значение свойства после анимации.

В некоторых случаях может показаться, что вы не можете изменить значение свойства после его анимации, даже после завершения анимации. Это связано с тем, что, несмотря на то, что анимация закончилась, она по-прежнему переопределяет базовое значение свойства. Чтобы остановить анимацию и избежать переопределения текущего значения свойства, удалите её или присвойте ему значение FillBehavior из Stop. Для получения дополнительной информации и примера см. раздел Установка свойства после его анимации с помощью Storyboard.

Изменение временной шкалы не влияет

Хотя большинство Timeline свойств являются анимируемыми и могут быть привязаны к данным, изменение значений свойств активного Timeline, кажется, не влияет. Это связано с тем, что при запуске Timeline система измерения времени делает копию Timeline и использует ее для создания объекта Clock. Изменение исходного файла не влияет на копию системы.

Чтобы Timeline мог отражать изменения, его часовой сигнал необходимо повторно создать и использовать для замены ранее созданного часового сигнала. Часы не восстанавливаются автоматически. Ниже приведено несколько способов применения изменений временной шкалы.

  • Если временная шкала относится к Storyboard, можно внести изменения, повторно применив её раскадровку с помощью BeginStoryboard или метода Begin. Это также имеет побочный эффект перезапуска анимации. В коде можно использовать метод Seek для возврата раскадровки обратно к предыдущей позиции.

  • Если вы применили анимацию непосредственно к свойству с помощью метода BeginAnimation, вызовите метод BeginAnimation еще раз и передайте его анимацию, которая была изменена.

  • Если вы работаете непосредственно на уровне часов, создайте и примените новый набор часов и используйте их для замены предыдущего набора созданных часов.

Дополнительные сведения о временных шкалах и часах см. в Обзор системы анимации и тайминга.

FillBehavior.Stop не работает должным образом

Иногда установка свойства FillBehavior на Stop, кажется, не действует, например, когда одна анимация передаёт управление другой, так как у неё установлен параметр HandoffBehaviorSnapshotAndReplace.

В следующем примере создаются Canvas, Rectangle и TranslateTransform. TranslateTransform будет анимироваться, чтобы перемещать Rectangle вокруг Canvas.

<Canvas Width="600" Height="200">
  <Rectangle 
    Canvas.Top="50" Canvas.Left="0" 
    Width="50" Height="50" Fill="Red">
    <Rectangle.RenderTransform>
      <TranslateTransform 
        x:Name="MyTranslateTransform" 
        X="0" Y="0" />
    </Rectangle.RenderTransform>
  </Rectangle>
</Canvas>

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

FillBehavior="Stop" и HandoffBehavior с несколькими анимациями

Иногда кажется, что анимация игнорирует своё свойство FillBehavior при замене второй анимацией. Выполните следующий пример, который создает два объекта Storyboard и использует их для анимации одного и того же TranslateTransform, показанного в предыдущем примере.

Первый StoryboardB1анимирует свойство X у TranslateTransform от 0 до 350, что перемещает прямоугольник на 350 пикселей вправо. Когда анимация достигнет конца его длительности и перестает воспроизводиться, свойство X возвращается к исходному значению 0. В результате прямоугольник перемещается вправо на 350 пикселей, а затем переходит обратно к исходной позиции.

<Button Content="Start Storyboard B1">
  <Button.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
      <BeginStoryboard>
        <Storyboard x:Name="B1">
          <DoubleAnimation 
            Storyboard.TargetName="MyTranslateTransform"
            Storyboard.TargetProperty="X"
            From="0" To="350" Duration="0:0:5"
            FillBehavior="Stop"
            />
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Button.Triggers>
</Button>

Второй Storyboard, B2, также анимирует свойство X того же TranslateTransform. Поскольку задано только свойство To анимации в этом Storyboard, анимация использует текущее значение свойства, которое оно анимирует, в качестве начального значения.


<!-- Animates the same object and property as the preceding
     Storyboard. -->
<Button Content="Start Storyboard B2">
  <Button.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
      <BeginStoryboard>
        <Storyboard x:Name="B2">
          <DoubleAnimation 
            Storyboard.TargetName="MyTranslateTransform"
            Storyboard.TargetProperty="X"
            To="500" Duration="0:0:5" 
            FillBehavior="Stop" />
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Button.Triggers>
</Button>

Если вы нажмете вторую кнопку, пока воспроизводится первая Storyboard, можно ожидать следующего поведения:

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

  2. Вторая раскадровка вступает в силу и анимируется с текущей позиции, которая сейчас 0, до 500.

Но это не то, что происходит. Вместо этого прямоугольник не прыгает назад; он продолжает двигаться справа. Это связано с тем, что вторая анимация использует текущее значение первой анимации в качестве начального значения и анимирует от этого значения до 500. Когда вторая анимация заменяет первую, так как используется SnapshotAndReplaceHandoffBehavior, FillBehavior первой анимации не имеет значения.

FillBehavior и завершенное событие

В следующих примерах демонстрируется еще один сценарий, в котором StopFillBehavior, кажется, не оказывает влияния. Снова в примере используется Storyboard для анимации свойства X объекта TranslateTransform с 0 до 350. Однако на этот раз пример регистрируется для события Completed.

<Button Content="Start Storyboard C">
  <Button.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
      <BeginStoryboard>
        <Storyboard Completed="StoryboardC_Completed">
          <DoubleAnimation 
            Storyboard.TargetName="MyTranslateTransform"
            Storyboard.TargetProperty="X"
            From="0" To="350" Duration="0:0:5"
            FillBehavior="Stop" />
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Button.Triggers>
</Button>

Обработчик событий Completed запускает еще один Storyboard, который анимирует то же свойство от текущего значения до 500.

private void StoryboardC_Completed(object sender, EventArgs e)
{

    Storyboard translationAnimationStoryboard =
        (Storyboard)this.Resources["TranslationAnimationStoryboardResource"];
    translationAnimationStoryboard.Begin(this);
}
Private Sub StoryboardC_Completed(ByVal sender As Object, ByVal e As EventArgs)

    Dim translationAnimationStoryboard As Storyboard = CType(Me.Resources("TranslationAnimationStoryboardResource"), Storyboard)
    translationAnimationStoryboard.Begin(Me)
End Sub

Ниже приведена разметка, которая определяет второй Storyboard в качестве ресурса.

<Page.Resources>
  <Storyboard x:Key="TranslationAnimationStoryboardResource">
    <DoubleAnimation 
      Storyboard.TargetName="MyTranslateTransform"
      Storyboard.TargetProperty="X"
      To="500" Duration="0:0:5" />
  </Storyboard>
</Page.Resources>

При запуске Storyboardможно ожидать, что свойство XTranslateTransform анимируется от 0 до 350, затем возвращается к 0 после завершения (так как оно имеет параметр FillBehaviorStop), а затем снова анимируется от 0 до 500. Вместо этого TranslateTransform анимирует от 0 до 350, а затем до 500.

Это связано с порядком, в котором WPF вызывает события и поскольку значения свойств кэшируются и не пересчитываются, если свойство не является недействительным. Событие Completed обрабатывается сначала, так как оно было активировано корневой временной шкалой (первый Storyboard). На данный момент свойство X по-прежнему возвращает анимированное значение, потому что оно еще не было аннулировано. Второй Storyboard использует кэшированное значение в качестве начального значения и начинает анимировать.

Производительность

Анимации продолжают выполняться после перехода на другую страницу

При переходе от Page, содержащей запущенные анимации, эти анимации будут продолжать воспроизводиться, пока Page не будет удален сборщиком мусора. В зависимости от используемой системы навигации, страница, с которой вы переходите, может оставаться в памяти неопределенное время, при этом потребляя ресурсы своими анимациями. Это наиболее заметно, когда страница содержит постоянно выполняемые ("внешние") анимации.

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

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

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

  • Чтобы удалить анимацию из определенного свойства, используйте метод BeginAnimation(DependencyProperty, AnimationTimeline). Укажите анимированное свойство в качестве первого параметра и null в качестве второго. Это приведет к удалению всех таймеров анимации из свойства.

Дополнительные сведения о различных способах анимации свойств см. в обзоре методов анимации свойств .

Использование Compose HandoffBehavior потребляет системные ресурсы

При применении Storyboard, AnimationTimelineили AnimationClock к свойству с помощью ComposeHandoffBehaviorвсе объекты Clock, ранее связанные с этим свойством, продолжают использовать системные ресурсы; Система времени не будет автоматически удалять эти часы.

Чтобы избежать проблем с производительностью при применении большого количества часов с помощью Compose, следует удалить составляющие часы из анимированного свойства после их завершения. Существует несколько способов удаления часов.

  • Чтобы удалить все часы из свойства, используйте метод ApplyAnimationClock(DependencyProperty, AnimationClock) или BeginAnimation(DependencyProperty, AnimationTimeline) анимированного объекта. Укажите анимированное свойство в качестве первого параметра и null в качестве второго. Это приведет к удалению всех таймеров анимации из свойства.

  • Чтобы удалить определенную AnimationClock из списка часов, используйте свойство ControllerAnimationClock для получения ClockController, а затем вызовите метод RemoveClockController. Обычно это делается в обработчике событий Completed для часов. Обратите внимание, что только корневые таймеры могут управляться ClockController; свойство Controller дочерних таймеров возвращает null. Обратите внимание также, что событие Completed не будет вызываться, если эффективная длительность часов навсегда. В этом случае пользователю потребуется определить, когда следует вызывать Remove.

Это в первую очередь проблема анимации для объектов с длительным временем существования. Когда объект подвергнут сборке мусора, его часы также будут отключены и подвергнуты сборке мусора.

Дополнительные сведения об объектах таймера см. в разделе Обзор системы анимации и синхронизации.

См. также