A quick look at Silverlight 3: Easing Animations

There are new capabilities for the animation system in Silverlight 3. Various classes such as the;

  • DoubleAnimation
  • PointAnimation
  • ColorAnimation

get a new dependency property called EasingFunction which is an instance of an interface IEasingFunction which has a single method on it;

double Ease(double normalizedTime);

the idea being that this can take a “normalised time” into an animation (e.g. a range from 0.0 to 1.0) and apply some function in order to determine “where” the animation should be at that particular point in time.

In Silverlight 2, there were special cases of this in that the standard animation types perform linear interpolation between two values and there’s also the KeyFrame based animations which allow you to specify particular values at discrete time intervals but Silverlight 3 comes along and generalises it ( you can implement IEasingFunction yourself and there’s a base class EasingFunctionBase to help ) and also provides some stock easing functions in the box;

  • BackEase
  • BounceEase
  • CircleEase
  • CubicEase
  • ElasticEase
  • ExponentialEase
  • PowerEase
  • QuadraticEase
  • QuarticEase
  • QuinticEase
  • SineEase

and each of these ( because of deriving from EasingFunctionBase ) pick up a property of type EasingMode which can be EaseOut, EaseIn and EaseInOut.

It’s easy to write some code to demonstrate these although note that each one has different properties to reflect its individual behaviour and I’m leaving all those properties to their default values here rather than try to write UI to display all the different ones;

<UserControl x:Class="SilverlightApplication19.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <UserControl.Resources>
        <Storyboard
            x:Name="sbMove"
            Storyboard.TargetName="ball">
            <DoubleAnimation
                x:Name="daLeft"
                To="384"
                Storyboard.TargetProperty="(Canvas.Left)"
                Duration="00:00:02" />
            <DoubleAnimation
                x:Name="daTop"
                To="384"
                Storyboard.TargetProperty="(Canvas.Top)"
                Duration="00:00:02" />
        </Storyboard>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Canvas
            Background="Silver">
            <Ellipse
                x:Name="ball"
                Canvas.Left="10"
                Canvas.Top="10"
                Fill="Red"
                Width="48"
                Height="48" />
        </Canvas>
        <StackPanel
            Grid.Row="1">
            <ComboBox
                Margin="10"
            x:Name="cmbEaseFunction"
            ItemsSource="{Binding}" />
        <Button
            Content="Run Storyboard"
            Margin="10"
            Click="OnRunStoryboard" />
        </StackPanel>
    </Grid>
</UserControl>

with code behind;

  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();

      this.Loaded += OnLoaded;
    }
    void OnLoaded(object sender, RoutedEventArgs e)
    {
      cmbEaseFunction.DataContext = easingFunctions;
    }
    void OnRunStoryboard(object sender, EventArgs args)
    {
      sbMove.Stop();
      daLeft.EasingFunction = cmbEaseFunction.SelectedItem as IEasingFunction;
      daTop.EasingFunction = cmbEaseFunction.SelectedItem as IEasingFunction;
      sbMove.Begin();
    }
    static IEasingFunction[] easingFunctions = 
    {
      new BackEase(),
      new BounceEase(),
      new CircleEase(),
      new CubicEase(),
      new ElasticEase(),
      new ExponentialEase(),
      new PowerEase(),
      new QuadraticEase(),
      new QuarticEase(),
      new QuinticEase(),
      new SineEase()
    };
  }

It’s a bit tricky to capture the output of this so I haven’t tried here but you probably get the idea that it’s an animated circle as below;

image

with the particular easing function being applied to its animation.

It’s also possible to define these functions in XAML (of couse :-)) and specify the easing modes on any of them as in this re-working of the original example;

<Storyboard

       x:Name="sbMove"

       Storyboard.TargetName="ball">

       <DoubleAnimation

           x:Name="daLeft"

           To="384"

           Storyboard.TargetProperty="(Canvas.Left)"

           Duration="00:00:02">

           <DoubleAnimation.EasingFunction>

               <ElasticEase

                   EasingMode="EaseInOut" />

           </DoubleAnimation.EasingFunction>

       </DoubleAnimation>

       <DoubleAnimation

           x:Name="daTop"

           To="384"

           Storyboard.TargetProperty="(Canvas.Top)"

           Duration="00:00:02">

           <DoubleAnimation.EasingFunction>

               <ElasticEase

                   EasingMode="EaseInOut" />

           </DoubleAnimation.EasingFunction>

       </DoubleAnimation>

   </Storyboard>

which causes the circle to bounce backwards and forwards around its start point before heading off to bounce backwards and forwards around its end point.

It’s also possible to use new classes such as;

  • EasingDoubleKeyFrame
  • EasingPointKeyFrame
  • EasingColorKeyFrame

to build an animation that puts together a number of easing functions at particular points in its lifecycle. Changing that previous example, I might have;

<Storyboard

     x:Name="sbMove"

     Storyboard.TargetName="ball">           
     <DoubleAnimationUsingKeyFrames

         x:Name="daLeft"

         Storyboard.TargetProperty="(Canvas.Left)"

         Duration="00:00:02">

         <EasingDoubleKeyFrame

             KeyTime="00:00:01"

             Value="198">

             <EasingDoubleKeyFrame.EasingFunction>

                 <ElasticEase />

             </EasingDoubleKeyFrame.EasingFunction>

         </EasingDoubleKeyFrame>

         <EasingDoubleKeyFrame

             KeyTime="00:00:02"

             Value="384">

             <EasingDoubleKeyFrame.EasingFunction>

                 <BounceEase />

             </EasingDoubleKeyFrame.EasingFunction>

         </EasingDoubleKeyFrame>

     </DoubleAnimationUsingKeyFrames>

     <DoubleAnimationUsingKeyFrames

         x:Name="daTop"

         Storyboard.TargetProperty="(Canvas.Top)"

         Duration="00:00:02">

         <EasingDoubleKeyFrame

             KeyTime="00:00:01"

             Value="198">

             <EasingDoubleKeyFrame.EasingFunction>

                 <ElasticEase />

             </EasingDoubleKeyFrame.EasingFunction>

         </EasingDoubleKeyFrame>

         <EasingDoubleKeyFrame

             KeyTime="00:00:02"

             Value="384">

             <EasingDoubleKeyFrame.EasingFunction>

                 <BounceEase />

             </EasingDoubleKeyFrame.EasingFunction>

         </EasingDoubleKeyFrame>

     </DoubleAnimationUsingKeyFrames>

</Storyboard>

which would then have my Ellipse use an ElasticEase for the first half of its animation and then a BounceEase to the end of its animation.

It’s easy to plug in your own easing functions ( much harder to come up with any new ones given how many are already plugged in to the framework ) – you could consider the “Null” easing function to be something like;

  public class NullEase : EasingFunctionBase
  {
    protected override double EaseInCore(double normalizedTime)
    {
      return (normalizedTime);
    }
  }

and then it’s up to you to decide what function you want to call to apply some factor to the incoming value (0 to 1) to produce the outgoing value (0 to 1).

You can download the code for this post from here.

This is one of a series of posts taking a quick look at Silverlight 3 – expect them to be a little “rough and ready” and that they’ll get expanded on as I’ve had more time to work with Silverlight 3.