Animações de quadro-chave e animações de função de atenuação

Animações lineares de quadro-chave, animações de quadro-chave com um valor KeySpline ou funções de atenuação são três técnicas diferentes para aproximadamente o mesmo cenário: criar uma animação com storyboard um pouco mais complexa e que usa um comportamento de animação não linear de um estado inicial para um estado final.

Pré-requisitos

Certifique-se de ter lido o tópico Animações com storyboard. Este tópico se baseia nos conceitos de animação que foram explicados em Animações com storyboard e não os abordará novamente. Por exemplo, animações com storyboard descrevem como direcionar animações, storyboards como recursos, os valores de propriedade Timeline, como Duration, FillBehavior e assim por diante.

Animação usando animações de quadro-chave

As animações de quadro-chave permitem mais de um valor de destino que é atingido em um ponto ao longo da linha do tempo da animação. Em outras palavras, cada quadro-chave pode especificar um valor intermediário diferente, e o último quadro-chave alcançado é o valor final da animação. Ao especificar vários valores para animar, você pode fazer animações mais complexas. As animações de quadro-chave também permitem uma lógica de interpolação diferente, cada uma implementada como uma subclasse KeyFrame diferente por tipo de animação. Especificamente, cada tipo de animação de quadro-chave tem uma variação Discreta, Linear, Spline e Easing de sua classe KeyFrame para especificar seus quadros-chave. Por exemplo, para especificar uma animação direcionada a um Double e usa quadros-chave, você pode declarar quadros-chave com DiscreteDoubleKeyFrame, LinearDoubleKeyFrame, SplineDoubleKeyFrame e EasingDoubleKeyFrame. Você pode usar todo e qualquer um desses tipos em uma única coleção KeyFrames para alterar a interpolação sempre que um novo quadro-chave for atingido.

Para o comportamento de interpolação, cada quadro-chave controla a interpolação até que seu tempo KeyTime seja atingido. Seu valor é alcançado naquele momento também. Se houver mais quadros-chave além, o valor se tornará o valor inicial para o próximo quadro-chave em uma sequência.

No início da animação, se não existir nenhum quadro-chave com KeyTime de "0:0:0", o valor inicial será qualquer que seja o valor não animado da propriedade. Isso é semelhante a como uma animação From/To/By age se não houver From.

A duração de uma animação de quadro-chave é implicitamente a duração igual ao valor KeyTime mais alto definido em qualquer um de seus quadros-chave. Você pode definir uma Duração explícita se quiser, mas tome cuidado para que não seja menor que um KeyTime em seus próprios quadros-chave ou você cortará parte da animação.

Além de Duration, você pode definir todas as propriedades baseadas em Timeline em uma animação de quadro-chave, como você pode fazer com uma animação From/To/By, porque as classes de animação de quadro-chave também derivam de Timeline. Estes são:

  • AutoReverse: uma vez atingido o último quadro-chave, os quadros são repetidos na ordem inversa a partir do final. Isso dobra a duração aparente da animação.
  • BeginTime: atrasa o início da animação. A linha do tempo para os valores KeyTime nos quadros não começa a contar até que BeginTime seja atingido, portanto, não há risco de cortar quadros
  • FillBehavior: controla o que acontece quando o último quadro-chave é atingido. FillBehavior não tem efeito em nenhum quadro-chave intermediário.
  • Comportamento de repetição:
    • Se definido como Para sempre, os quadros-chave e sua linha do tempo se repetem infinitamente.
    • Se definido como uma contagem de iteração, a linha do tempo repete isso muitas vezes.
    • Se definido como uma Duração, a linha do tempo se repete até que esse tempo seja atingido. Isso pode truncar a animação no meio da sequência de quadros-chave, se não for um fator inteiro da duração implícita da linha do tempo.
  • SpeedRatio (não comumente usado)

Quadros-chave lineares

Os quadros-chave lineares resultam em uma interpolação linear simples do valor até que o KeyTime do quadro seja atingido. Esse comportamento de interpolação é o mais semelhante às animações From/To/By mais simples descritas no tópico Animações com storyboard.

Veja como usar uma animação de quadro-chave para dimensionar a altura de renderização de um retângulo, usando quadros-chave lineares. Este exemplo executa uma animação em que a altura do retângulo aumenta ligeiramente e linearmente nos primeiros 4 segundos e, em seguida, é dimensionada rapidamente no último segundo até que o retângulo tenha o dobro da altura inicial.

<StackPanel>
    <StackPanel.Resources>
        <Storyboard x:Name="myStoryboard">
            <DoubleAnimationUsingKeyFrames
              Storyboard.TargetName="myRectangle"
              Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)">
                <LinearDoubleKeyFrame Value="1" KeyTime="0:0:0"/>
                <LinearDoubleKeyFrame Value="1.2" KeyTime="0:0:4"/>
                <LinearDoubleKeyFrame Value="2" KeyTime="0:0:5"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </StackPanel.Resources>
</StackPanel>

Quadros-chave discretos

Quadros-chave discretos não usam nenhuma interpolação. Quando um KeyTime é atingido, o novo valor é simplesmente aplicado. Dependendo de qual propriedade da interface do usuário está sendo animada, isso geralmente produz uma animação que parece "pular". Certifique-se de que esse é o comportamento estético que você realmente deseja. Você pode minimizar os saltos aparentes aumentando o número de quadros-chave declarados, mas se uma animação suave for seu objetivo, talvez seja melhor usar quadros-chave lineares ou spline.

Observação

Quadros-chave discretos são a única maneira de animar um valor que não seja do tipo Double, Point e Color, com um DiscreteObjectKeyFrame. Discutiremos isso com mais detalhes posteriormente neste tópico.

Quadros-chave de spline

Um quadro-chave de spline cria uma transição variável entre os valores de acordo com o valor da propriedade KeySpline . Essa propriedade especifica o primeiro e o segundo pontos de controle de uma curva de Bézier, que descreve a aceleração da animação. Basicamente, uma KeySpline define uma relação função-sobre-tempo em que o gráfico função-tempo é a forma dessa curva de Bézier. Normalmente, você especifica um valor KeySpline em uma cadeia de caracteres de atributo abreviado XAML que tem quatro valores Double separados por espaços ou vírgulas. Esses valores são pares "X,Y" para dois pontos de controle da curva de Bézier. "X" é o tempo e "Y" é o modificador de função para o valor. Cada valor deve estar sempre entre 0 e 1 inclusive. Sem a modificação do ponto de controle em uma KeySpline, a linha reta de 0,0 a 1,1 é a representação de uma função ao longo do tempo para uma interpolação linear. Seus pontos de controle alteram a forma dessa curva e, portanto, o comportamento da função ao longo do tempo para a animação de spline. Provavelmente é melhor ver isso visualmente como um gráfico. Você pode executar o exemplo de visualizador de spline de chave do Silverlight em um navegador para ver como os pontos de controle modificam a curva e como uma animação de exemplo é executada ao usá-la como um valor KeySpline .

O próximo exemplo mostra três quadros-chave diferentes aplicados a uma animação, sendo o último uma animação de spline de chave para um valor Double (SplineDoubleKeyFrame). Observe a string "0.6,0.0 0.9,0.00" aplicada ao KeySpline. Isso produz uma curva em que a animação parece ser executada lentamente no início, mas atinge rapidamente o valor pouco antes de o KeyTime ser atingido.

<Storyboard x:Name="myStoryboard">
    <!-- Animate the TranslateTransform's X property
        from 0 to 350, then 50,
        then 200 over 10 seconds. -->
    <DoubleAnimationUsingKeyFrames
        Storyboard.TargetName="MyAnimatedTranslateTransform"
        Storyboard.TargetProperty="X"
        Duration="0:0:10" EnableDependentAnimation="True">

        <!-- Using a LinearDoubleKeyFrame, the rectangle moves 
            steadily from its starting position to 500 over 
            the first 3 seconds.  -->
        <LinearDoubleKeyFrame Value="500" KeyTime="0:0:3"/>

        <!-- Using a DiscreteDoubleKeyFrame, the rectangle suddenly 
            appears at 400 after the fourth second of the animation. -->
        <DiscreteDoubleKeyFrame Value="400" KeyTime="0:0:4"/>

        <!-- Using a SplineDoubleKeyFrame, the rectangle moves 
            back to its starting point. The
            animation starts out slowly at first and then speeds up. 
            This KeyFrame ends after the 6th second. -->
        <SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="0" KeyTime="0:0:6"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

Flexibilização de quadros-chave

Um quadro-chave de atenuação é um quadro-chave em que a interpolação está sendo aplicada e a função ao longo do tempo da interpolação é controlada por várias fórmulas matemáticas predefinidas. Na verdade, você pode produzir o mesmo resultado com um quadro-chave spline e com alguns dos tipos de função de atenuação, mas também existem algumas funções de atenuação, como BackEase, que não podem ser reproduzidas com uma spline.

Para aplicar uma função de easing a um quadro-chave de easing, defina a propriedade EasingFunction como um elemento de propriedade em XAML para esse quadro-chave. Para o valor, especifique um elemento de objeto para um dos tipos de função de atenuação.

Este exemplo aplica um CubicEase e, em seguida, um BounceEase como quadros-chave sucessivos a um DoubleAnimation para criar um efeito de salto.

<Storyboard x:Name="myStoryboard">
    <DoubleAnimationUsingKeyFrames Duration="0:0:10"
        Storyboard.TargetProperty="Height"
        Storyboard.TargetName="myEllipse">

        <!-- This keyframe animates the ellipse up to the crest 
            where it slows down and stops. -->
        <EasingDoubleKeyFrame Value="-300" KeyTime="00:00:02">
            <EasingDoubleKeyFrame.EasingFunction>
                <CubicEase/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>

        <!-- This keyframe animates the ellipse back down and makes
            it bounce. -->
        <EasingDoubleKeyFrame Value="0" KeyTime="00:00:06">
            <EasingDoubleKeyFrame.EasingFunction>
                <BounceEase Bounces="5"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

Este é apenas um exemplo de função de atenuação. Abordaremos mais na próxima seção.

Funções de easing

As funções easing permitem aplicar fórmulas matemáticas personalizadas às suas animações. As operações matemáticas geralmente são úteis para produzir animações que simulam a física do mundo real em um sistema de coordenadas 2D. Por exemplo, talvez você queira que um objeto ricocheteie de modo realista ou comporte-se como se estivesse em uma mola. Você poderia usar animações de quadro-chave ou até mesmo de De/Para/Para para aproximar esses efeitos, mas isso exigiria uma quantidade significativa de trabalho e a animação seria menos precisa do que usar uma fórmula matemática.

As funções de easing podem ser aplicadas às animações de três maneiras:

Aqui está uma lista das funções de atenuação:

Algumas das funções de easing têm suas próprias propriedades. Por exemplo, BounceEase tem duas propriedades Bounces e Bounciness que modificam o comportamento de função ao longo do tempo desse BounceEase específico. Outras funções de atenuação, como CubicEase, não têm propriedades diferentes da propriedade EasingMode que todas as funções de atenuação compartilham e sempre produzem o mesmo comportamento de função ao longo do tempo.

Algumas dessas funções de easing têm um pouco de sobreposição, dependendo de como você define propriedades nas funções de easing que têm propriedades. Por exemplo, QuadraticEase é exatamente o mesmo que PowerEase com Power igual a 2. E CircleEase é basicamente um ExponentialEase de valor padrão.

A função de easing BackEase é exclusiva porque pode alterar o valor fora do intervalo normal, conforme definido por From/To ou valores de quadros-chave. Ele inicia a animação alterando o valor na direção oposta, como seria esperado de um comportamento De/Para normal, volta para o valor De ou inicial novamente e, em seguida, executa a animação normalmente.

Em um exemplo anterior, mostramos como declarar uma função de atenuação para uma animação de quadro-chave. Este próximo exemplo aplica uma função de atenuação a uma animação De/Para/por.

<StackPanel x:Name="LayoutRoot" Background="White">
    <StackPanel.Resources>
        <Storyboard x:Name="myStoryboard">
            <DoubleAnimation From="30" To="200" Duration="00:00:3" 
                Storyboard.TargetName="myRectangle" 
                Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)">
                <DoubleAnimation.EasingFunction>
                    <BounceEase Bounces="2" EasingMode="EaseOut" 
                                Bounciness="2"/>
                </DoubleAnimation.EasingFunction>
            </DoubleAnimation>
        </Storyboard>
    </StackPanel.Resources>
    <Rectangle x:Name="myRectangle" Fill="Blue" Width="200" Height="30"/>
</StackPanel>

Quando uma função de atenuação é aplicada a uma animação De/Para/por, ela está alterando as características de função ao longo do tempo de como o valor interpola entre os valores De e Para durante a duração da animação. Sem uma função de atenuação, isso seria uma interpolação linear.

Animações de valor de objeto discreto

Um tipo de animação merece menção especial porque é a única maneira de aplicar um valor animado a propriedades que não são do tipo Double, Point ou Color. Esta é a animação de quadro-chave ObjectAnimationUsingKeyFrames. A animação usando valores de objeto é diferente porque não há possibilidade de interpolar os valores entre os quadros. Quando o KeyTime do quadro é atingido, o valor animado é imediatamente definido como o valor especificado no Valor do quadro-chave. Como não há interpolação, há apenas um quadro-chave que você usa na coleção de quadros-chave ObjectAnimationUsingKeyFrames: DiscreteObjectKeyFrame.

O valor de um DiscreteObjectKeyFrame geralmente é definido usando a sintaxe do elemento de propriedade, pois o valor do objeto que você está tentando definir geralmente não é expressável como uma cadeia de caracteres para preencher o valor na sintaxe do atributo. Você ainda pode usar a sintaxe de atributo se usar uma referência como StaticResource.

Um lugar em que você verá um ObjectAnimationUsingKeyFrames usado nos modelos padrão é quando uma propriedade de modelo faz referência a um recurso Brush. Esses recursos são objetos SolidColorBrush, não apenas um valor Color, e usam recursos definidos como temas do sistema (ThemeDictionaries). Eles podem ser atribuídos diretamente a um valor do tipo Brush, como TextBlock.Foreground , e não precisam usar o direcionamento indireto. Mas como um SolidColorBrush não é Double, Point ou Color, você precisa usar um ObjectAnimationUsingKeyFrames para usar o recurso.

<Style x:Key="TextButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid Background="Transparent">
                    <TextBlock x:Name="Text"
                        Text="{TemplateBinding Content}"/>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPointerOverForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPressedForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
...
                       </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Você também pode usar ObjectAnimationUsingKeyFrames para animar propriedades que usam um valor de enumeração. Aqui está outro exemplo de um estilo nomeado que vem dos modelos padrão do Tempo de Execução do Windows. Observe como ele define a propriedade Visibility que usa uma constante de enumeração Visibility. Nesse caso, você pode definir o valor usando a sintaxe de atributo. Você só precisa do nome da constante não qualificada de uma enumeração para definir uma propriedade com um valor de enumeração, por exemplo, "Recolhido".

<Style x:Key="BackButtonStyle" TargetType="Button">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="Button">
          <Grid x:Name="RootGrid">
            <VisualStateManager.VisualStateGroups>
              <VisualStateGroup x:Name="CommonStates">
              <VisualState x:Name="Normal"/>
...           <VisualState x:Name="Disabled">
                <Storyboard>
                  <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame Value="Collapsed" KeyTime="0"/>
                  </ObjectAnimationUsingKeyFrames>
                </Storyboard>
              </VisualState>
            </VisualStateGroup>
...
          </VisualStateManager.VisualStateGroups>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Você pode usar mais de um DiscreteObjectKeyFrame para um conjunto de quadros ObjectAnimationUsingKeyFrames. Essa pode ser uma maneira interessante de criar uma animação de "apresentação de slides" animando o valor de Image.Source, como um cenário de exemplo para onde vários valores de objeto podem ser úteis.