Partilhar via


Dicas e truques de animação

When working with animations in WPF, there are a number of tips and tricks that can make your animations perform better and save you frustration.

General Issues

Animating the Position of a Scroll Bar or Slider Freezes It

If you animate the position of a scroll bar or slider using an animation that has a FillBehavior of HoldEnd (the default value), the user will no longer be able to move the scroll bar or slider. That's because, even though the animation ended, it's still overriding the target property's base value. To stop the animation from overriding the property's current value, remove it, or give it a FillBehavior of Stop. For more information and an example, see Como: Definir uma Propriedade Após Animá-la com um Storyboard.

Animating the Output of an Animation Has No Effect

You can't animate an object that is the output of another animation. For example, if you use an ObjectAnimationUsingKeyFrames to animate the Fill of a Rectangle from a RadialGradientBrush to a SolidColorBrush, you can't animate any properties of the RadialGradientBrush or SolidColorBrush.

Can't Change the Value of a Property after Animating it

In some cases, it might appear that you can't change the value of a property after it's been animated, even after the animation has ended. That's because, even though the animation ended, it's still overriding the property's base value. To stop the animation from overriding the property's current value, remove it, or give it a FillBehavior of Stop. For more information and an example, see Como: Definir uma Propriedade Após Animá-la com um Storyboard.

Changing a Timeline Has No Effect

Although most Timeline properties are animatable and can be data bound, changing the property values of an active Timeline seems to have no effect. That's because, when a Timeline is begun, the timing system makes a copy of the Timeline and uses it to create a Clock object. Modifying the original has no effect on the system's copy.

For a Timeline to reflect changes, its clock must be regenerated and used to replace the previously created clock. Clocks are not regenerated for you automatically. The following are several ways to apply timeline changes:

  • If the timeline is or belongs to a Storyboard, you can make it reflect changes by reapplying its storyboard using a BeginStoryboard or the Begin method. This has the side effect of also restarting the animation. In code, you can use the Seek method to advance the storyboard back to its previous position.

  • If you applied an animation directly to a property using the BeginAnimation method, call the BeginAnimation method again and pass it the animation that has been modified.

  • If you are working directly at the clock level, create and apply a new set of clocks and use them to replace the previous set of generated clocks.

For more information about timelines and clocks, see Visão Geral de Animação e Sistema de Tempo.

FillBehavior.Stop Doesn't Work as Expected

There are times when setting the FillBehavior property to Stop seems to have no effect, such as when one animation "hands off" to another because it has a HandoffBehavior setting of SnapshotAndReplace.

The following example creates a Canvas, a Rectangle and a TranslateTransform. The TranslateTransform will be animated to move the Rectangle around the 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>

The examples in this section use the preceding objects to demonstrate several cases where the FillBehavior property doesn't behave as you might expect it to.

FillBehavior="Stop" and HandoffBehavior with Multiple Animations

Sometimes it seems as though an animation ignores its FillBehavior property when it is replaced by a second animation. Take the following example, which creates two Storyboard objects and uses them to animate the same TranslateTransform shown in the preceding example.

The first Storyboard, B1, animates the X property of the TranslateTransform from 0 to 350, which moves the rectangle 350 pixels to the right. Quando a animação atinge o fim da duração e interrompe a execução, o X propriedade reverte para seu valor original, 0. Como resultado, o retângulo se move para os pixels da direita 350 e saltos de volta à 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>

The second Storyboard, B2, also animates the X property of the same TranslateTransform. Because only the To property of the animation in this Storyboard is set, the animation uses the current value of the property it animates as its starting value.


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

If you click the second button while the first Storyboard is playing, you might expect the following behavior:

  1. The first storyboard ends and sends the rectangle back to its original position, because the animation has a FillBehavior of Stop.

  2. The second storyboard takes effect and animates from the current position, which is now 0, to 500.

But that's not what happens. Instead, the rectangle does not jump back; it continues moving to the right. Isso ocorre porque a segunda animação usa o valor atual da primeira animação como seu valor inicial e anima o valor para 500. Quando a segunda animação substitui a primeira, porque o SnapshotAndReplace HandoffBehavior é usado, o FillBehavior da primeira animação não importa.

FillBehavior and the Completed Event

The next examples demonstrate another scenario in which the Stop FillBehavior seems to have no effect. Novamente, o exemplo usa um Storyboard para animar o X propriedade da TranslateTransform de 0 até 350. No entanto, desta vez o exemplo registra o Completed de evento.

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

The Completed event handler starts another Storyboard that animates the same property from its current value to 500.

        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
private void StoryboardC_Completed(object sender, EventArgs e)
{

    Storyboard translationAnimationStoryboard =
        (Storyboard)this.Resources["TranslationAnimationStoryboardResource"];
    translationAnimationStoryboard.Begin(this);
}

The following is the markup that defines the second Storyboard as a resource.

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

Quando você executa o Storyboard, você poderia esperar a X propriedade da TranslateTransform para animar de 0 a 350, em seguida, reverter para 0 após a sua conclusão (porque ele tem um FillBehavior configuração de Stop) e animar o de 0 a 500. Em vez disso, o TranslateTransform anima de 0 a 350 e 500.

Que é devido a ordem na qual WPF gera eventos e porque os valores de propriedade são armazenados em cache e não são recalculadas, a menos que a propriedade é invalidada. The Completed event is processed first because it was triggered by the root timeline (the first Storyboard). At this time, the X property still returns its animated value because it hasn't been invalidated yet. The second Storyboard uses the cached value as its starting value and begins animating.

Performance

Animations Continue to Run After Navigating Away from a Page

When you navigate away from a Page that contains running animations, those animations will continue to play until the Page is garbage collected. Depending on the navigation system you're using, a page that you navigate away from might stay in memory for an indefinite amount of time, all the while consuming resources with its animations. This is most noticeable when a page contains constantly running ("ambient") animations.

For this reason, it's a good idea to use the Unloaded event to remove animations when you navigate away from a page.

There are different ways to remove an animation. The following techniques can be used to remove animations that belong to a Storyboard.

The next technique may be used regardless of how the animation was started.

For more information about the different ways to animate properties, see Visão geral de técnicas de animação de propriedades.

Using the Compose HandoffBehavior Consumes System Resources

When you apply a Storyboard, AnimationTimeline, or AnimationClock to a property using the Compose HandoffBehavior, any Clock objects previously associated with that property continue to consume system resources; the timing system will not remove these clocks automatically.

To avoid performance issues when you apply a large number of clocks using Compose, you should remove composing clocks from the animated property after they complete. There are several ways to remove a clock.

This is primarily an issue for animations on objects that have a long lifetime. When an object is garbage collected, its clocks will also be disconnected and garbage collected.

For more information about clock objects, see Visão Geral de Animação e Sistema de Tempo.

Consulte também

Conceitos

Revisão de Animação