In much of the writing about animation in Silverlight, one question that seems to recur is this: why bother with a linear key-frame animation if it is less intuitive and more complex than a simple from-to animation
Let’s back up and examine that for just a second.
Linear Interpolation
Linear Interpolation means that if you instruct the animation to start at position 0 and move to position 200 over 4 seconds, then you know that it will be at position 100 at 2 seconds, (half way there in half the time) and at position 50 at 1 second (1/4 of the way in 1/4 of the time) and at position 25 at 1/2 second. That is, the distance is covered evenly over the allotted time.
It turns out that there are two types of animation in Silverlight that use linear interpolation: simple from-to animation, and Linear Keyframe animation.
From-To Animation
From-to animation just says “Take this value and change its value from x to y over this much time”. For example, you might write
<DoubleAnimation Storyboard.TargetName=”TT1” Storyboard.TargetProperty=”X”
From=”0” To=”500” Duration=”0:0:2” />
DoubleAnimation means that you’re going to create an animation by changing some value that is expressed as a Double (that is a number that might have a fraction).
The Target is named TT1 (in this case it is a Transform but it could be a shape, etc.) The property on that target is X (in this case the X position in an (X,Y) point. Then you see the From value (0) and the To value (500) and the Duration (2 seconds) which means that you’re going to change that property (x) from 0 to 500 over 2 seconds.
A From To Example
Suppose you want to move a rectangle from the upper left to the lower right of your control, and you’re writing the Xaml by hand. One very easy way to do so is to use From-to animation (specifically, a DoubleAnimation that targets the Rectangle’s TranslateTransform X and Y). You might end up with a Page.xaml that looks like this:
1: <UserControl x:Class="BlogAnimationExample.Page"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: Width="600" Height="800">
5: <UserControl.Resources>
6: <Storyboard x:Name="FT1">
7: <DoubleAnimation
8: Storyboard.TargetName="TT1"
9: Storyboard.TargetProperty="X"
10: From="0" To="500" Duration="0:0:2" />
11: <DoubleAnimation
12: Storyboard.TargetName="TT1"
13: Storyboard.TargetProperty="Y"
14: From="0" To="700" Duration="0:0:2" />
15: </Storyboard>
16:
17: </UserControl.Resources>
18: <Grid x:Name="LayoutRoot" Background="White" >
19: <Rectangle x:Name="rect1"
20: HorizontalAlignment="Left"
21: VerticalAlignment="Top"
22: Margin="10,0,0,0"
23: Stroke="#FFFF0000"
24: Width="200" Height="60">
25: <Rectangle.RenderTransform>
26: <TransformGroup>
27: <TranslateTransform x:Name="TT1" />
28: </TransformGroup>
29: </Rectangle.RenderTransform>
30: </Rectangle>
31: </Grid>
32: </UserControl>
You need something to kick that off, so modify Page.xaml.cs as follows:
1: public Page()
2: {
3: InitializeComponent();
4: Loaded += new RoutedEventHandler( Page_Loaded );
5: }
6:
7: void Page_Loaded( object sender, RoutedEventArgs e )
8: {
9: FT1.Begin();
10: }
Now the animation will begin as soon as the page loads.
Adding a Wrinkle
That works fine, but what if instead of having the rectangle drift to the lower right on an angle, what you’d like is for the rectangle to go straight down, then to the right and then return home on the angle?
If you look at our story board you can see that it is combining the first DoubleAnimation that moves to the right with the second animation that moves down to create a single path that moves down and to the right at the same time. What is needed is three story boards!
1: <UserControl.Resources>
2: <Storyboard x:Name="FT1">
3: <!--Move down-->
4: <DoubleAnimation
5: Storyboard.TargetName="TT1"
6: Storyboard.TargetProperty="Y"
7: From="0" To="700" Duration="0:0:2" />
8: </Storyboard>
9: <Storyboard x:Name="FT2">
10: <!--Move right-->
11: <DoubleAnimation
12: Storyboard.TargetName="TT1"
13: Storyboard.TargetProperty="X"
14: From="0" To="500" Duration="0:0:2" />
15: </Storyboard>
16: <Storyboard x:Name="FT3">
17: <!--Move home-->
18: <DoubleAnimation
19: Storyboard.TargetName="TT1"
20: Storyboard.TargetProperty="X"
21: From="500" To="0" Duration="0:0:2" />
22: <DoubleAnimation
23: Storyboard.TargetName="TT1"
24: Storyboard.TargetProperty="Y"
25: From="700" To="0" Duration="0:0:2" />
26: </Storyboard>
27: </UserControl.Resources>
The first storyboard moves down (and not to the right), the second moves right, and the third moves both on both the x and y axis to move home. Unfortunately, you’re not done. Because of multi-tasking you can’t just launch these; you have to make sure that FT1 has completed before you start FT2, and that must complete in turn before you launch FT3.
Fortunately, there is a completed event for storyboards, so it isn’t all that difficult:
1: public Page()
2: {
3: InitializeComponent();
4: Loaded += new RoutedEventHandler( Page_Loaded );
5: FT1.Completed += new EventHandler( FT1_Completed );
6: FT2.Completed += new EventHandler( FT2_Completed );
7: }
8:
9: void FT2_Completed( object sender, EventArgs e )
10: {
11: FT3.Begin();
12: }
13:
14: void FT1_Completed( object sender, EventArgs e )
15: {
16: FT2.Begin();
17: }
18:
19: void Page_Loaded( object sender, RoutedEventArgs e )
20: {
21: FT1.Begin();
22: }
It isn’t pretty, but it works.
Using Keyframes
Keyframes give you a good bit more control at the cost of a little more complexity. With Keyframes you state a series of time/value pairs.
So rather than just saying here’s my start value and my end value and how much time you have you say at time 0 the value is 0 and at time 1 second it is 120 and at time 1.27 seconds it is 195 and at time 2 seconds it is 400.
You can have as many time elements as you want, and they don’t have to be evenly spaced. While the system will create a linear interpolation between the values you supply, you do not have to space your explicitly named values evenly. So it is perfectly legitimate to tell the shape to move 50 units in the first second, 200 units in the next two seconds and 500 units in the fourth second.
That extra power allows you to solve the problem above with a single storyboard:
1: <UserControl.Resources>
2: <Storyboard x:Name="StoryBoard1" >
3: <DoubleAnimationUsingKeyFrames
4: Storyboard.TargetName="TT1"
5: Storyboard.TargetProperty="X">
6: <LinearDoubleKeyFrame KeyTime="0:0:0" Value="0" />
7: <LinearDoubleKeyFrame KeyTime="0:0:2" Value="0" />
8: <LinearDoubleKeyFrame KeyTime="0:0:4" Value="500" />
9: <LinearDoubleKeyFrame KeyTime="0:0:6" Value="0" />
10: </DoubleAnimationUsingKeyFrames>
11: <DoubleAnimationUsingKeyFrames
12: Storyboard.TargetName="TT1"
13: Storyboard.TargetProperty="Y">
14: <LinearDoubleKeyFrame KeyTime="0:0:0" Value="0" />
15: <LinearDoubleKeyFrame KeyTime="0:0:2" Value="700" />
16: <LinearDoubleKeyFrame KeyTime="0:0:4" Value="700" />
17: <LinearDoubleKeyFrame KeyTime="0:0:6" Value="0" />
18: </DoubleAnimationUsingKeyFrames>
19: </Storyboard>
20: </UserControl.Resources>
21: <Grid x:Name="LayoutRoot" Background="White" ShowGridLines="false
22: <Rectangle x:Name="rect1"
23: HorizontalAlignment="Left"
24: VerticalAlignment="Top"
25: Margin="10,0,0,10"
26: Stroke="#FFFF0000"
27: Width="200"
28: Height="60" >
29: <Rectangle.RenderTransform>
30: <TransformGroup>
31: <ScaleTransform />
32: <RotateTransform />
33: <TranslateTransform x:Name="TT1" />
34: </TransformGroup>
35: </Rectangle.RenderTransform>
36: </Rectangle>
37: </Grid>
Because the keyframe allows me to hold the Y value in place after I move it, I can wait there while I move right, creating my own synchronization as needed without creating multiple storyboards, and giving a much cleaner, easier to understand and maintain animation.