Compartilhar via


Dicas e truques de animação

Ao trabalhar com animações no WPF, há várias dicas e truques que podem fazer com que suas animações sejam melhores e salvem a frustração.

Problemas gerais

Animar a posição de uma barra de rolagem ou controle deslizante os deixa congelados

Se você animar a posição de uma barra de rolagem ou controle deslizante usando uma animação que tenha uma FillBehavior de HoldEnd (o valor padrão), o usuário não poderá mais mover a barra de rolagem ou o controle deslizante. Isso acontece porque, mesmo que a animação tenha terminado, ela ainda estará substituindo o valor base da propriedade de destino. Para impedir que a animação substitua o valor atual da propriedade, remova-a ou atribua-lhe um FillBehavior de Stop. Para obter mais informações e um exemplo, consulte Definir uma propriedade após animá-la com um storyboard.

Animar a saída de uma animação não tem nenhum efeito

Não é possível animar um objeto que seja a saída de outra animação. Por exemplo, se você usar um ObjectAnimationUsingKeyFrames para animar o Fill de um Rectangle de um RadialGradientBrush para um SolidColorBrush, não será possível animar nenhuma propriedade do RadialGradientBrush ou SolidColorBrush.

Não é possível alterar o valor de uma propriedade após animá-la

Em alguns casos, pode parecer que você não pode alterar o valor de uma propriedade depois que ela for animada, mesmo após o término da animação. Isso acontece porque, mesmo que a animação tenha terminado, ela ainda estará substituindo o valor base da propriedade. Para impedir que a animação substitua o valor atual da propriedade, remova-a ou atribua-lhe um FillBehavior de Stop. Para obter mais informações e um exemplo, consulte Definir uma propriedade após animá-la com um storyboard.

Alterar uma linha do tempo não tem efeito

Embora a maioria das propriedades Timeline seja animatável e possa ser associada a dados, alterar os valores de propriedade de um Timeline ativo parece não ter efeito. Isso porque, quando um Timeline é iniciado, o sistema de tempo faz uma cópia do Timeline e o usa para criar um objeto Clock. Modificar o original não tem efeito na cópia do sistema.

Para que um Timeline reflita as alterações, seu relógio deve ser regenerado e usado para substituir o relógio criado anteriormente. Os relógios não são regenerados automaticamente para você. Veja a seguir várias maneiras de aplicar alterações na linha do tempo:

  • Se a linha do tempo for de um Storyboard ou pertencer a ele, você poderá fazer com que ela reflita as alterações reaplicando o storyboard dela através do uso de um BeginStoryboard ou do método Begin. Isso tem o efeito colateral de também reiniciar a animação. No código, você pode usar o método Seek para avançar o storyboard de volta para sua posição anterior.

  • Se você aplicou uma animação diretamente a uma propriedade usando o método BeginAnimation, chame o método BeginAnimation novamente e passe a animação que foi modificada.

  • Se você estiver trabalhando diretamente no nível do relógio, crie e aplique um novo conjunto de relógios e use-os para substituir o conjunto anterior de relógios gerados.

Para obter mais informações sobre linhas do tempo e relógios, consulte Visão geral do sistema de animação e temporização.

FillBehavior.Stop não funciona conforme o esperado

Há momentos em que definir a propriedade FillBehavior como Stop parece não ter efeito, como quando uma animação passa o controle para outra porque tem HandoffBehavior definido como SnapshotAndReplace.

O exemplo a seguir cria um Canvas, um Rectangle e um TranslateTransform. O TranslateTransform será animado para mover o Rectangle pelo 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>

Os exemplos nesta seção usam os objetos anteriores para demonstrar vários casos em que a propriedade FillBehavior não se comporta como você poderia esperar.

FillBehavior="Stop" e HandoffBehavior com várias animações

Às vezes, parece que uma animação ignora sua propriedade FillBehavior quando é substituída por uma segunda animação. Veja o exemplo a seguir, que cria dois objetos Storyboard e os usa para animar o mesmo TranslateTransform mostrado no exemplo anterior.

O primeiro Storyboard, B1, anima a propriedade X do TranslateTransform de 0 a 350, que move o retângulo 350 pixels para a direita. Quando a animação atinge o final de sua duração e para de reproduzir, a propriedade X é revertida para seu valor original, 0. Como resultado, o retângulo se move 350 pixels para a direita e, em seguida, salta de volta para sua posição original.

<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>

O segundo Storyboard, B2, também anima a propriedade X do mesmo TranslateTransform. Como apenas a propriedade To da animação neste Storyboard está definida, a animação usa o valor atual da propriedade que anima como seu valor inicial.


<!-- 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>

Se você clicar no segundo botão enquanto o primeiro Storyboard estiver sendo reproduzido, você poderá esperar o seguinte comportamento:

  1. O primeiro storyboard termina e envia o retângulo de volta à sua posição original, pois a animação tem um FillBehavior definido como Stop.

  2. O segundo storyboard entra em vigor e anima, da posição atual, que agora é 0, até a posição 500.

Mas não é isso que acontece. Em vez disso, o retângulo não salta para trás; continua se movendo para a direita. Isso ocorre porque a segunda animação usa o valor atual da primeira animação como seu valor inicial e anima desse valor para 500. Quando a segunda animação substitui a primeira porque a SnapshotAndReplaceHandoffBehavior é usada, a FillBehavior da primeira animação não importa.

FillBehavior e o evento concluído

Os exemplos a seguir demonstram outro cenário no qual o StopFillBehavior parece não ter efeito. Novamente, o exemplo usa um Storyboard para animar a propriedade X do TranslateTransform de 0 a 350. No entanto, desta vez, o exemplo se inscreve para o evento 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>

O manipulador de eventos Completed inicia um outro Storyboard que anima a mesma propriedade do seu valor atual até 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

Veja a seguir a marcação que define o segundo Storyboard como um recurso.

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

Ao executar o Storyboard, você pode esperar que a propriedade X do TranslateTransform anime de 0 a 350 e, em seguida, reverta para 0 após a conclusão (porque ela tem uma configuração FillBehavior de Stop) e anime de 0 a 500. Em vez disso, o TranslateTransform anima de 0 até 350 e depois até 500.

Isso ocorre devido à ordem na qual o WPF gera eventos e porque os valores da propriedade são armazenados em cache e não são recalculados, a menos que a propriedade seja invalidada. O evento Completed é processado primeiro porque foi disparado pelo cronograma raiz (o primeiro Storyboard). Neste momento, a propriedade X ainda retorna seu valor animado, uma vez que ainda não foi invalidada. O segundo Storyboard usa o valor armazenado em cache como seu valor inicial e começa a animar.

Desempenho

Animações continuam a ser executadas depois de navegar para longe de uma página

Quando você sai de uma Page que contenha animações em execução, as animações continuarão a ser executadas até que a Page passe pela coleta de lixo. Dependendo do sistema de navegação que você está usando, uma página da qual você saiu pode permanecer na memória por tempo indefinido, enquanto consome recursos devido às suas animações. Isso é mais perceptível quando uma página contém animações em execução constante ("ambient").

Por esse motivo, é uma boa ideia usar o evento Unloaded para remover animações quando você navega para longe de uma página.

Há diferentes maneiras de remover uma animação. As técnicas a seguir podem ser usadas para remover animações que pertencem a um Storyboard.

A próxima técnica pode ser usada independentemente de como a animação foi iniciada.

  • Para remover animações de uma propriedade específica, use o método BeginAnimation(DependencyProperty, AnimationTimeline). Especifique a propriedade que está sendo animada como o primeiro parâmetro e null como a segunda. Isso removerá todos os relógios de animação da propriedade.

Para obter mais informações sobre as diferentes maneiras de animar propriedades, consulte Property Animation Techniques Overview.

O uso do HandoffBehavior composto consome recursos do sistema

Quando você aplica um Storyboard, AnimationTimelineou AnimationClock a uma propriedade usando o ComposeHandoffBehavior, todos os objetos Clock anteriormente associados a essa propriedade continuam a consumir recursos do sistema; o sistema de tempo não removerá esses relógios automaticamente.

Para evitar problemas de desempenho quando você aplica um grande número de relógios usando Compose, remova os relógios de composição da propriedade animada após a conclusão deles. Há várias maneiras de remover um relógio.

Esse é um problema principalmente para animações em objetos que têm um longo tempo de vida. Quando um objeto passa pela coleta de lixo, seus relógios também serão desconectados e coletados como lixo.

Para obter mais informações sobre objetos de relógio, consulte Visão geral do sistema de animação e temporização.

Consulte também

  • Visão geral da animação