Custom page transitions in WP7

The Silverlight for Windows Phone Toolkit provides the mechanism for creating animated page transitions in your Windows Phone 7 applications. It also provides a few standard transitions that match the look and feel of the built in apps such as pages sliding in, rotating out, etc. But what if I want to create a new transition that doesn’t exist in the toolkit? What if we want to run an arbitrary storyboard as the page transition? Then we need to build a new class that fits into the toolkit’s page transition framework.

First a little background. In order to use the page transitions from the toolkit you need a couple of things added to your page. (Starting from a brand new “Windows Phone Application” project

The Microsoft.Phone.Controls.Toolkit assembly needs to be referenced in your app.
assemblyref

Change app.xaml.cs so the RootFrame is a TransitionFrame instead of a PhoneApplicationFrame

 1 RootFrame = new Microsoft.Phone.Controls.TransitionFrame();

The toolkit namespace needs to be referenced in you page’s XAML

 1     xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

Add the transition service to the page you want to see transitions on. e.g. in MainPage.xaml (anywhere inside the Page element)

  1     <toolkit:TransitionService.NavigationInTransition>
 2         <toolkit:NavigationInTransition>
 3             <toolkit:NavigationInTransition.Backward>
 4                 <toolkit:TurnstileTransition Mode="BackwardIn"/>
 5             </toolkit:NavigationInTransition.Backward>
 6             <toolkit:NavigationInTransition.Forward>
 7                 <toolkit:TurnstileTransition Mode="ForwardIn"/>
 8             </toolkit:NavigationInTransition.Forward>
 9         </toolkit:NavigationInTransition>
10     </toolkit:TransitionService.NavigationInTransition>
11 
12     <toolkit:TransitionService.NavigationOutTransition>
13         <toolkit:NavigationOutTransition>
14             <toolkit:NavigationOutTransition.Backward>
15                 <toolkit:TurnstileTransition Mode="BackwardOut"/>
16             </toolkit:NavigationOutTransition.Backward>
17             <toolkit:NavigationOutTransition.Forward>
18                 <toolkit:TurnstileTransition Mode="ForwardOut"/>
19             </toolkit:NavigationOutTransition.Forward>
20         </toolkit:NavigationOutTransition>
21     </toolkit:TransitionService.NavigationOutTransition>

Then add a second page and add buttons to the MainPage that navigate to the second page. Make the same changes to the second page’s XAML that you made to the MainPage (namespace & transition elements) and you will see the turnstile animation every time you go forward or backwards between the two pages. Very cool!

Now what we want to do is replace those toolkit:TurnstileTransition elements with something of our own. What I want to do is have the current page spin out and the next page spin back in (sort of like the old Batman TV series scene transitions). First I need to create a new transition type that will support arbitrary Storyboards instead of fixed ones.

What we need to provide (instead of the TurnstileTransition class) is our own class that implements TransitionElement. Add a new class to the project named StoryboardTransition and derive it from TransitionElement. TransitionElement only requires one method GetTransition. Define it as follows

 1 public class StoryboardTransition : TransitionElement
2 {
3     public override ITransition GetTransition(UIElement element)
4     {
5         return new Transition(element, this.Storyboard);
6     }
7 }

GetTransition will be called by the transition service on the page navigation event and will hand us a UIElement (which will be the page). We will return an ITransition which allows the service to manage the animation. There is a nice utility class called Transition in the Silverlight Toolkit which wraps a storyboard with an ITransition interface, but it is currently marked internal. I’ve filed a request to get it changed to public but in the meantime just copy that class into your project. It is located in the source at <Phone Toolkit Src>\Microsoft.Phone.Controls.Toolkit\Transitions\Transitions.cs

Next add a property that we can set the storyboard we want to use for transitions in the XAML.

  1 public const string StoryboardPropertyName = "Storyboard";
 2 
 3 public Storyboard Storyboard
 4 {
 5     get { return (Storyboard)GetValue(StoryboardProperty); }
 6     set { SetValue(StoryboardProperty, value); }
 7 }
 8 
 9 public static readonly DependencyProperty StoryboardProperty =
10     DependencyProperty.Register(StoryboardPropertyName, typeof(Storyboard), typeof(StoryboardTransition), new PropertyMetadata(null));

Now lets add the transition storyboards as resources to the page. Make sure they are added above the transition service definition. Also make sure to include default Transforms for whatever properties you animate (LayoutRoot in this example). You can also just use Expression Blend to create whatever animation you want.

  1 <phone:PhoneApplicationPage.Resources>
 2         <Storyboard x:Key="BatmanTransitionOut" 
 3             xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
 4             xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
 5         <DoubleAnimation Duration="0:0:0.5" To="5000" From="0" Storyboard.TargetName="LayoutRoot"  Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationZ)">
 6             <DoubleAnimation.EasingFunction>
 7                 <ExponentialEase EasingMode="EaseIn" Exponent="5"/>
 8             </DoubleAnimation.EasingFunction>
 9         </DoubleAnimation>
10         <DoubleAnimation Duration="0:0:0.5" To="0.1" From="1.0" Storyboard.TargetName="LayoutRoot"  Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)"/>
11         <DoubleAnimation Duration="0:0:0.5" To="0.1" From="1.0" Storyboard.TargetName="LayoutRoot"  Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"/>
12     </Storyboard>
13     <Storyboard x:Key="BatmanTransitionIn"
14         xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
15         xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
16         <DoubleAnimation Duration="0:0:0.5" From="5000" To="0" Storyboard.TargetName="LayoutRoot"  Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationZ)">
17             <DoubleAnimation.EasingFunction>
18                 <ExponentialEase EasingMode="EaseOut" Exponent="5"/>
19             </DoubleAnimation.EasingFunction>
20         </DoubleAnimation>
21         <DoubleAnimation Duration="0:0:0.5" From="0.1" To="1.0" Storyboard.TargetName="LayoutRoot"  Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)"/>
22         <DoubleAnimation Duration="0:0:0.5" From="0.1" To="1.0" Storyboard.TargetName="LayoutRoot"  Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"/>
23         </Storyboard>
24 </phone:PhoneApplicationPage.Resources>
 1     <Grid x:Name="LayoutRoot" Background="Transparent" RenderTransformOrigin="0.5,0.5">
2         <!-- Batman transition requires default transforms -->
3         <Grid.RenderTransform>
4             <CompositeTransform/>
5         </Grid.RenderTransform>
6         <Grid.Projection>
7             <PlaneProjection/>
8         </Grid.Projection>

Add the StoryboardTransition class’s namespace to the page

 1 xmlns:local="clr-namespace:StoryboardTransitions"

Modify the original NavigationService declaration to use the BatmanIn & BatmanOut transitions via the StoryboardTransition class we built.

  1 <toolkit:TransitionService.NavigationInTransition>
 2     <toolkit:NavigationInTransition>
 3         <toolkit:NavigationInTransition.Backward>
 4             <local:StoryboardTransition Storyboard="{StaticResource BatmanTransitionIn}"/>
 5         </toolkit:NavigationInTransition.Backward>
 6         <toolkit:NavigationInTransition.Forward>
 7             <local:StoryboardTransition Storyboard="{StaticResource BatmanTransitionIn}"/>
 8         </toolkit:NavigationInTransition.Forward>
 9     </toolkit:NavigationInTransition>
10 </toolkit:TransitionService.NavigationInTransition>
11 
12 <toolkit:TransitionService.NavigationOutTransition>
13     <toolkit:NavigationOutTransition>
14         <toolkit:NavigationOutTransition.Backward>
15             <local:StoryboardTransition Storyboard="{StaticResource BatmanTransitionOut}"/>
16         </toolkit:NavigationOutTransition.Backward>
17         <toolkit:NavigationOutTransition.Forward>
18             <local:StoryboardTransition Storyboard="{StaticResource BatmanTransitionOut}"/>
19         </toolkit:NavigationOutTransition.Forward>
20     </toolkit:NavigationOutTransition>
21 </toolkit:TransitionService.NavigationOutTransition>
22 

Now update Page 2 the same way as the main page (storyboard, default transforms, additional namespace, and transition declaration), and run your app. Switch back and forth between the main page and the second page and watch them spin back and forth.

Ideally, you would not have your transition storyboard replicated in every page. You would have it in a file that you referenced so that changes would be centralized and easy to make. When experimenting with this I eventually moved the storyboards to a separate XAP file like the toolkit does and added a StoryboardFilepath property to the StoryboardTransition class, but I left the original Storyboard property intact and just gave priority to one over the other in the GetTransition method. You can’t put the storyboards into the application resources because funny things seem to happen when sharing storyboards across pages.

The power of the Storyboard property here is that you can use Expression Blend to modify your animations and immediately see the effects in your app. This is great for iterative development and once you have your effect nailed down, just copy it out to a file for code reuse. This local storyboard method also allows you to do animations that depend on particular elements on the page itself whereas the generic navigation animations only animate the whole LayoutRoot object.

[UPDATE]

The Transition class is public as of changeset 60019. Thanks guys! If you build your own toolkit from the source you can just derive directly from their version of Transition now instead of copying the class locally. Of course it will also eventually show up in an official release.

As Forrest Gump said “That's good. One less thing."

[UPDATE]

See part 2 to add functionality that loads the storyboards from XAP files instead of requiring them to be in each page’s resource dictionary.

StoryboardTransitions.zip