If you like the idea of giving the user some visual
feedback from a button, but the 360° spin is just a bit too
ostentatious, perhaps jiggling the button a little might be more polite.
So you open a new project named JiggleButtonTryout and begin experimenting.
Let’s start with just one Button with a TranslateTransform set to the RenderTransform property:
Example 1. Silverlight Project: JiggleButtonTryout File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Button Content="Jiggle Button" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"> <Button.RenderTransform> <TranslateTransform x:Name="translate" /> </Button.RenderTransform> </Button> </Grid>
|
In the Resources collection you’ll want to define a Storyboard:
<phone:PhoneApplicationPage.Resources>
<Storyboard x:Name="jiggleStoryboard">
</Storyboard>
</phone:PhoneApplicationPage.Resources>
The code-behind file starts the animation when the button is clicked:
Example 2. Silverlight Project: JiggleButtonTryout File: MainPage.xaml.cs (excerpt)
void OnButtonClick(object sender, RoutedEventArgs args) { jiggleStoryboard.Begin(); }
|
Perhaps the first thing you try is a DoubleAnimation that animates the X property of the TranslateTransform:
<Storyboard x:Name="jiggleStoryboard">
<DoubleAnimation Storyboard.TargetName="translate"
Storyboard.TargetProperty="X"
From="-10" To="10" Duration="0:0:0.05"
AutoReverse="True"
RepeatBehavior="3x" />
</Storyboard>
The result looks vaguely
OK, but it’s not quite right, because the animation initially jumps the
button to the left 10 pixels, and then the animation goes from left to
right and back again three times. (Notice the XAML syntax for repeating
the animation three times or “3x”.) Then it stops with the button still
10 units to the left. You can see the problem more clearly if you change
the offsets to –100 and 100, and the duration to ½ second.
One way to fix part of the problem is to set the FillBehavior to Stop,
which releases the animation at the end, causing the button to jump
back to its original position. But that creates another discontinuous
jump at the end of the animation besides the one at the beginning.
To make this correct, we
really need a couple different animations. We first want to animate from
0 to –10, then from –10 to 10 and back again a few times, and then
finally back to zero. Fortunately, Silverlight has a facility to string
animations like this in a sequence. It’s called a key-frame animation,
and the first step is to replace DoubleAnimation with DoubleAnimationUsingKeyFrames. Everything except the TargetName and TargetProperty doesn’t apply in new approach:
<Storyboard x:Name="jiggleStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="translate"
Storyboard.TargetProperty="X">
</DoubleAnimationUsingKeyFrames>
</Storyboard>
When you’re animating properties of type double, DoubleAnimationUsingKeyFrames is the only alternative to DoubleAnimation, but it gives you a lot more flexibility. The DoubleAnimationUsingKeyFrames class has children of type DoubleKeyFrame, and you have four choices:
DiscreteDoubleKeyFrame jumps to a particular position
LinearDoubleKeyFame performs a linear animation
SplineDoubleKeyFrame can speed up and slow down
EasingDoubleKeyFrame animates with an easing function
For now, I want to use DiscreteDoubleKeyFrame and LinearDoubleKeyFrame. Each keyframe object requires two properties to be set: a KeyTime and a Value. The KeyTime is an elapsed time from the beginning of the animation; the Value property is the desired value of the target property at that time.
To get a better view of what’s
happening, let’s swing the button very wide, and let’s do it slowly. At
time zero, we want the value to be zero:
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
This isn’t actually
required, because 0 is the value of the target property at the beginning
of the animationanyway, but it doesn’t hurt.
At the end of 1 second, let’s set the value to be –100:
<LinearDoubleKeyFrame KeyTime="0:0:01" Value="-100" />
The use of LinearDoubleKeyFrame here means that in the duration from time zero to 1 second, the X property of TranslateTransform will change linearly from 0 to –100. The velocity is 100 units a second, so to keep the same velocity for the swing to 100, the next key time should be three seconds:
<LinearDoubleKeyFrame KeyTime="0:0:03" Value="100" />
This means that from an
elapsed time of 1 second to an elapsed time of 3 seconds, the value
changes from –100 to 100. Finally, another elapsed second brings it back
to the starting position:
<LinearDoubleKeyFrame KeyTime="0:0:04" Value="0" />
If you try this out, you can
see that the button moves left, then all the way right, then back to the
center without any jumps in a total of 4 seconds. The total duration of a key-frame animation is the maximum KeyTime on all the key-frame objects.
Now let’s perform that entire maneuver three times:
<Storyboard x:Name="jiggleStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="translate"
Storyboard.TargetProperty="X"
RepeatBehavior="3x">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<LinearDoubleKeyFrame KeyTime="0:0:01" Value="-100" />
<LinearDoubleKeyFrame KeyTime="0:0:03" Value="100" />
<LinearDoubleKeyFrame KeyTime="0:0:04" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
The total show lasts for 12 seconds but without any discontinuities.
Now that the button has the desired behavior, the offsets can be reduced from 100 to 10:
<Storyboard x:Name="jiggleStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="translate"
Storyboard.TargetProperty="X"
RepeatBehavior="3x">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<LinearDoubleKeyFrame KeyTime="0:0:01" Value="-10" />
<LinearDoubleKeyFrame KeyTime="0:0:03" Value="10" />
<LinearDoubleKeyFrame KeyTime="0:0:04" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
To bring the time values
down to something reasonable, I want to show you a little trick. Often
when you’re developing animations you want to do run them very slowly to
get them working correctly, and then you want to speed them up for the final version. Of course, you could go through and adjust all the KeyTime values, or you could simply specify a SpeedRatio on the animation, as in the version of the animation in the JiggleButtonTryout project:
Example 3. Silverlight Project: JiggleButtonTryout File: MainPage.xaml (excerpt)
<phone:PhoneApplicationPage.Resources> <Storyboard x:Name="jiggleStoryboard"> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="translate" Storyboard.TargetProperty="X" RepeatBehavior="3x" SpeedRatio="40"> <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" /> <LinearDoubleKeyFrame KeyTime="0:0:01" Value="-10" /> <LinearDoubleKeyFrame KeyTime="0:0:03" Value="10" /> <LinearDoubleKeyFrame KeyTime="0:0:04" Value="0" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </phone:PhoneApplicationPage.Resources>
|
Each cycle of the key frames requires 4 seconds; this is repeated 3 times for a total of 12 seconds, but the SpeedRatio value of 40 effectively speeds up the animation by a factor of 40 so it’s only 0.3 seconds total.
If you want to immortalize this effect in a custom control called JiggleButton for easy reusability, you have a few choices, none of which are entirely satisfactory.
You could derive from UserControl and incorporate the Button and the transform in that control. But to do this right would require reproducing all the ButtonUserControl properties and events. Another approach involves a template. Perhaps the easiest option is to derive from Button, but in doing so you’d have to appropriate the RenderTransform property for this specific purpose, and the RenderTransform property would be unusable for other purposes. properties and events as