Applying storyboard animations to multiple XAML objects
[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]
Here's how to avoid having to create multiple storyboard animations, and instead apply the same storyboard to multiple objects.
Look at this XAML page from an app, displaying a grid of keypad images. When the user taps on one of these buttons, we want to animate it to make it look like it's moved.
As we've seen in the topic Skinning and animating a button, unlike iOS in which we vary parameters such as opacity over time in an animation block, Windows 8 uses storyboard objects. If you have several similar XAML objects on-screen, for example, our grid of buttons, you might be wondering if we need to create a unique storyboard for each. The good news is that we don't: and here is how to apply the same animation to multiple objects.
Note Remember that in Windows 8, a storyboard is an animation, not a way to lay out your application as in Xcode.
The secret is to programmatically change the storyboard's TargetName property, as the following example will demonstrate.
Defining the storyboard
First, define the storyboard animation. You can either use Blend, or define it manually in XAML. For an example using Blend, see Skinning and animating a button. If you do use Blend, remove any TargetName property, or the animation will be applied only to that specific target. Here's an example:
<Page.Resources>
<Storyboard x:Name="TapAnimation">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" >
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.05" Value="0.95"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" >
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.05" Value="0.95"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
Remember to add a <RenderTransform> tag to any XAML object your storyboard will change, as well as a placeholder for the specific transform being animated, or your app will throw an exception. Here's an example of an Image object ready to have the storyboard applied:
<Image x:Name="myImage" Width="256" Height="160" RenderTransformOrigin= "0.5,0.5" >
<Image.RenderTransform>
<CompositeTransform/>
</Image.RenderTransform>
</Image>
Note The use of the RenderTransformOrigin= "0.5,0.5" property ensures any animation is centered around the middle of the object.
Applying the storyboard
Next, associate the storyboard with an object and trigger it as required. There is one caveat - you cannot change the TargetName property while the animation is playing or the app will throw an exception. To prevent this, call Stop() before changing the target.
Tip Calling GetCurrentState() can detect whether a storyboard is currently running.
Here's the code to trigger the animation for a specific Image object. For example, this code could be in response to the user tapping the image using an PointerPressed event. It's just as easy to apply it to a Button, using a Click event.
// Add using Windows.UI.Xaml.Media.Animation;
TapAnimation.Stop();
TapAnimation.SetValue(Storyboard.TargetNameProperty,"myImage");
TapAnimation.Begin();
This approach is even more useful when you generate the new target name automatically from the control which triggered the event, like this:
private void someImages_PointerPressed(object sender, PointerRoutedEventArgs e)
{
TapImage.Stop();
TapImage.SetValue(Storyboard.TargetNameProperty, (sender as Image).Name);
TapImage.Begin();
}
Here the Image object the user has tapped creates the event, and we use its name as the target. If all the images on the page generate the same event when tapped, they will all be animated with the same storyboard. So, in our keypad example, each key will be animated and we only need to create one storyboard.
Note You may not have seen the as keyboard before: it performs a cast on the sender to make it an Image.
Multiple buttons, one event handler
With an app with multiple, similar buttons it makes sense to have one master event handler, rather than an event handler for each button. Every button can use the same click event, but we need to be a way to differentiate between buttons in code: this is a good place to use tags.
As in iOS, every control or object can have a tag: a value which you can use to uniquely identify them. For example, in our keypad example, we could define the buttons as in this XAML: notice how the buttons are nearly identical but differ by tag value.
<Button Content="1" HorizontalAlignment="Left" Margin="446,78,0,0" VerticalAlignment="Top" Width="120" Height="120" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="1">
<Button.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="#FF838383" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
</Button>
<Button Content="2" HorizontalAlignment="Left" Margin="446,543,0,0" VerticalAlignment="Top" Width="460" Height="120" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="2">
<Button.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="#FF838383" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
</Button>
<Button Content="3" HorizontalAlignment="Left" Margin="446,393,0,0" VerticalAlignment="Top" Width="120" Height="120" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="3">
<Button.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="#FF838383" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
</Button>
All the buttons point to the same event handler, Button_Click. Here is how we can read the tag value and react appropriately within the handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
var tag = (sender as Button).Tag;
int t = Convert.ToInt16(tag);
switch (t)
{
case 0: break;
case 1: break;
case 2: break;
default: break;
}
}
Related topics
Working with Animations Programmatically
How to: Create User Interfaces Using XAML and Expression Blend