Sugerencias y trucos para animaciones

Cuando se trabaja con animaciones en WPF, hay varias sugerencias y trucos que pueden mejorar el rendimiento de las animaciones y evitar muchas frustraciones.

Problemas generales

Al animar la posición de una barra de desplazamiento o de un control deslizante, se inmoviliza

Si anima la posición de una barra de desplazamiento o de un control deslizante utilizando una animación cuyo FillBehavior es HoldEnd (el valor predeterminado), el usuario ya no podrá mover la barra de desplazamiento o el control deslizante. Esto se debe a que, aunque la animación finaliza, continúa invalidando el valor base de la propiedad de destino. Para que la animación deje de invalidar el valor actual de la propiedad, quítelo o asígnele un FillBehavior de Stop. Para obtener más información y un ejemplo, consulte Establecer una propiedad después de animarla con un guion gráfico.

Animar la salida de una animación no surte ningún efecto

No se puede animar ningún objeto que sea la salida de otra animación. Por ejemplo, si usa un objeto ObjectAnimationUsingKeyFrames para animar el Fill de un Rectangle de RadialGradientBrush a SolidColorBrush, no puede animar ninguna propiedad de RadialGradientBrush o SolidColorBrush.

No se puede cambiar el valor de una propiedad después de animarla

En algunos casos, puede parecer que no es posible cambiar el valor de una propiedad después de animarla, incluso después de que la animación haya finalizado. Esto se debe a que, aunque la animación finaliza, continúa invalidando el valor base de la propiedad. Para que la animación deje de invalidar el valor actual de la propiedad, quítelo o asígnele un FillBehavior de Stop. Para obtener más información y un ejemplo, consulte Establecer una propiedad después de animarla con un guion gráfico.

Cambiar una escala de tiempo no surte ningún efecto

Aunque la mayoría de las propiedades de Timeline se pueden animar y enlazar a datos, cambiar los valores de propiedad de un objeto Timeline activo parece no surtir ningún efecto. Esto se debe a que, cuando Timeline se inicia, el sistema de control de tiempo realiza una copia de Timeline y la utiliza para crear un objeto Clock. Modificar el original no surte ningún efecto en la copia del sistema.

Para que Timeline refleje los cambios, deberá volver a generarse su reloj y utilizarlo para reemplazar el reloj previamente creado. Los relojes no se regeneran automáticamente. A continuación se muestran distintas maneras de aplicar cambios a las escalas de tiempo:

  • Si la escala de tiempo es un objeto Storyboard o pertenece a esta clase, para reflejar los cambios puede volver a aplicar su guion gráfico a través de BeginStoryboard o del método Begin. El efecto secundario de esta acción es que también se reinicia la animación. En código, puede usar el método Seek para avanzar el guion gráfico de vuelta a su posición anterior.

  • Si aplicó una animación directamente a una propiedad utilizando el método BeginAnimation, llame de nuevo el método BeginAnimation y pásele la animación que se ha modificado.

  • Si está trabajando directamente en el nivel de relojes, cree y aplique un nuevo conjunto de relojes y utilícelos para reemplazar el conjunto anterior de relojes generados.

Para más información sobre escalas de tiempo y relojes, consulte Información general sobre sistemas de control de tiempo y animación.

FillBehavior.Stop no funciona como se espera

En ocasiones, parece que establecer la propiedad FillBehavior en Stop no surte ningún efecto, como cuando una animación "se entrega" a otra porque el valor de su propiedad SnapshotAndReplace es HandoffBehavior.

En el ejemplo siguiente, se crea un Canvas, un Rectangle y una TranslateTransform. Se animará TranslateTransform para mover el Rectangle alrededor del 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>

En los ejemplos de esta sección se utilizan los objetos anteriores para mostrar varios casos en que la propiedad FillBehavior no se comporta como cabría esperar.

FillBehavior="Stop" y HandoffBehavior con varias animaciones

A veces, parece como si una animación omitiese su propiedad FillBehavior cuando la reemplaza una segunda animación. Tomemos el ejemplo siguiente, en el que se crean dos objetos Storyboard y se utilizan para animar la misma TranslateTransform mostrada en el ejemplo anterior.

El primer Storyboard, B1, anima la propiedad X de TranslateTransform desde 0 hasta 350, lo que mueve el rectángulo 350 píxeles a la derecha. Cuando la animación alcanza el fin de su duración y su reproducción se detiene, la propiedad X revierte a su valor original, 0. Como resultado, el rectángulo se mueve 350 píxeles a la derecha y luego salta para situarse en su posición 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>

El segundo Storyboard, B2, también anima la propiedad X de la misma TranslateTransform. Dado que se establece únicamente la propiedad To de la animación en este Storyboard, la animación utiliza el valor actual de la propiedad que anima como su 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>

Si hace clic en el segundo botón mientras el primer Storyboard se está reproduciendo, cabría esperar el comportamiento siguiente:

  1. El primer guion gráfico finaliza y envía el rectángulo a su posición original, porque el valor de la propiedad Stop de la animación es FillBehavior.

  2. El segundo guion gráfico se lleva a efecto y anima el objeto a partir de la posición actual, que ahora es 0, hasta 500.

Pero esto no es lo que sucede. En lugar de ello, el rectángulo no salta a su posición original, sino que continúa moviéndose a la derecha. Esto se debe a que la segunda animación utiliza el valor actual de la primera animación como su valor inicial y anima desde ese valor hasta 500. Cuando la segunda animación reemplaza a la primera porque se utiliza el valor SnapshotAndReplaceHandoffBehavior, el valor de FillBehavior de la primera animación no se tiene en cuenta.

FillBehavior y el evento Completed

En los ejemplos siguientes se muestra otro escenario en el que el valor StopFillBehavior parece no surtir ningún efecto. Una vez más, en el ejemplo se un Storyboard para animar la propiedad X de una TranslateTransform de 0 a 350. Sin embargo, esta vez en el ejemplo se efectúa el registro para el 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>

El controlador de eventos Completed inicia otro Storyboard que anima la misma propiedad desde su valor actual hasta 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

A continuación, se muestra el marcado que define el segundo Storyboard como recurso.

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

Al ejecutar el objeto Storyboard, cabría esperar que la propiedad X de TranslateTransform se animara de 0 a 350, y luego revertiera a 0 después de completarse (dado que su valor de FillBehavior es Stop), para, por último, animarse desde 0 hasta 500. En su lugar, el TranslateTransform anima desde 0 a 350 y luego a 500.

Esto se debe al orden en el que WPF genera los eventos y al hecho de que los valores de propiedad se almacenan en la memoria caché y no se vuelven a calcular a menos que se invalide la propiedad. El evento Completed se procesa en primer lugar porque lo activa la escala de tiempo raíz (el primer Storyboard). En este momento, la propiedad X sigue devolviendo el valor animado porque todavía no se ha invalidado. El segundo Storyboard utiliza el valor almacenado en caché como su valor inicial y comienza la animación.

Rendimiento

Las animaciones siguen ejecutándose después de salir de una página

Cuando se navega para salir de un control Page que contiene animaciones en ejecución, estas continuarán reproduciéndose hasta que se efectúe la recolección de elementos no utilizados de Page. Según el sistema de navegación que se utilice, la página de la que ha salido al navegar podría permanecer en memoria por tiempo indefinido, durante el cual seguiría consumiendo recursos con sus animaciones. Esto resulta especialmente patente cuando una página contiene animaciones de ejecución continua ("ambiente").

Por esta razón, es conveniente utilizar el evento Unloaded para quitar las animaciones cuando se sale de una página al navegar.

Hay diferentes maneras de quitar una animación. Las técnicas siguientes se pueden utilizar para quitar animaciones que pertenecen a un objeto Storyboard.

La técnica siguiente se puede utilizar sin tener en cuenta cómo se inició la animación.

  • Para quitar animaciones de una propiedad específica, utilice el método BeginAnimation(DependencyProperty, AnimationTimeline). Especifique la propiedad que se va a animar como primer parámetro y null como segundo parámetro. De este modo, se quitarán todos los relojes de animación de la propiedad.

Para obtener más información sobre los distintos modos de animar propiedades, consulte Información general sobre técnicas de animación de propiedades.

Utilizar el valor Compose de HandoffBehavior consume recursos del sistema

Cuando se aplica un objeto Storyboard, AnimationTimeline o AnimationClock a una propiedad utilizando el valor Compose de HandoffBehavior, los objetos Clock asociados con anterioridad a esa propiedad siguen utilizando recursos del sistema; el sistema de control de tiempo no quitará estos relojes automáticamente.

Para evitar problemas de rendimiento cuando aplique muchos relojes mediante Compose, debe quitar los relojes de composición de la propiedad animada cuando se hayan completado. Hay varias formas de quitar un reloj.

Este problema se produce principalmente en las animaciones de objetos que tienen un período de duración prolongado. Cuando un objeto se recolecta como elemento no utilizado, sus relojes también se desconectan y se recolectan como elementos no utilizados.

Para obtener más información acerca de los objetos de reloj, consulte Información general sobre sistemas de control de tiempo y animación.

Vea también