This is the third post is a series about creating simple page transitions using Storyboards. We’ve created two basic page transitions with the slide and turnstile transition. Now it’s time to get a little more complex. The mail app has what I like to call a “fly away” transition. When you tap an email, the subject flies away and the email slides up.
This transition is a little more complex. Instead of animating the entire page, we only animate one control that is contained within one item of a ListBox. When the selection changes we need to animate the “header” of the selected item. The problem is that the SelectedItem of a ListBox is the bound item, and not the UI representation of that item. Good news is if we are displaying items with any type of ItemsControl, we can get the container for the bound item. An ItemsControl has a ItemContainerGenerator property that returns an instance of an ItemContainerGenerator. The ItemContainerGenerator has a ContainerFromItem method that returns the DependencyObject used to display the bound item.
DependencyObject obj = itemsControl.ItemContainerGenerator.ContainerFromItem(selectedItem);
When we have that item, we need to get the first child that is a TextBlock.
private static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
Once we have the first TextBlock, we’ll need to animate it to the left and down. Like the Slide transition, we’ll animate the TranslateY and TranslateX properties of a CompositeTransform. We’ve been creating a lot of DoubleAnimations, so let’s make a method to help.
private static DoubleAnimation CreateDoubleAnimation(FrameworkElement element, double duration,
double from, double to, string path)
{
var animation = new DoubleAnimation { Duration = TimeSpan.FromSeconds(duration), To = to, From = from };
Storyboard.SetTargetProperty(animation, new PropertyPath(path));
Storyboard.SetTarget(animation, element);
return animation;
}
From here the transition is very similar to the others.
public static async Task TransitionOutFlyaway(this ItemsControl source, object item)
{
await source.Flyaway(item, 0, 480, 0, 100);
}
public static async Task TransitionInFlyaway(this ItemsControl source, object item)
{
await source.Flyaway(item, -480, 0, -100, 0);
}
private static async Task Flyaway(this ItemsControl source, object item, double xFrom, double xTo, double yFrom, double yTo)
{
if (source == null) return;
if (item == null) return;
DependencyObject obj = source.ItemContainerGenerator.ContainerFromItem(item);
if (obj == null) return;
TextBlock textBlock = obj.GetChildOfType<TextBlock>();
if (textBlock == null) return;
var story = new Storyboard();
var xAnimation = CreateDoubleAnimation(textBlock, .2, xFrom, xTo, "(UIElement.RenderTransform).(CompositeTransform.TranslateX)");
var yAnimation = CreateDoubleAnimation(textBlock, .2, yFrom, yTo, "(UIElement.RenderTransform).(CompositeTransform.TranslateY)");
story.Children.Add(xAnimation);
story.Children.Add(yAnimation);
await story.BeginAsync();
}
In the video above, I used the Windows Phone Databound App project template. By default, this template uses the new LongListSelector. Unfortunately, the LongListSelector is not supported for this transition. The LongListSelector is not an ItemsControl, and as such, does not have a way to get the generated UI element from a bound item. To use this project, you’ll need to change the LongListSelector to a ListBox.
Now that we have our transition complete, we’ll need to use it! When the selection changes, we’ll call our new extension method. We’ll also need to store the selected item to ensure that we can animate it back in when the back is navigated to.
private async void MainLongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// If selected item is null (no selection) do nothing
if (MainLongListSelector.SelectedItem == null)
return;
// save the item so we can animate it when coming back.
_selectedItem = MainLongListSelector.SelectedItem;
try
{
await MainLongListSelector.TransitionOutFlyaway(_selectedItem);
}
catch { }
// Navigate to the new page
NavigationService.Navigate(new Uri("/DetailsPage.xaml?selectedItem=" + (_selectedItem as ItemViewModel).ID, UriKind.Relative));
// Reset selected item to null (no selection)
MainLongListSelector.SelectedItem = null;
}
When the page is navigated to, we’ll animate the item back in.
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
try
{
await MainLongListSelector.TransitionInFlyaway(_selectedItem);
}
catch { }
// set it to null to ensure it is not animated at the wrong time.
_selectedItem = null;
}
You can download the sample application from this post which contains all of the transitions so far and uses the animations like in the video above.
In my last post, I talked about creating a simple transition to slide a page in or out. While that transition was functionality I needed for my app, Santa Calls, I thought it would be fun to do more of these posts. For this post we’ll create a turnstile transition. The turnstile transition is like a page turning.
For the turnstile transition, we need to use the animate the Projection of the page. We’ll set the Projection to be a PlaneProjection. To get the rotation to turn like a page would, we need to rotate around the vertical, or y-axis. The RotationY property of the PlaneProjection is how we accomplish this.
public static async Task TransitionInTurnstile(this FrameworkElement source)
{
await source.Turnstile(75, 0);
}
public static async Task TransitionOutTurnstile(this FrameworkElement source)
{
await source.Turnstile(0, -90);
}
private static async Task Turnstile(this FrameworkElement source, double from, double to)
{
if (source == null) return;
// make sure the projection pivots on the left side of the phone
source.Projection = new PlaneProjection { CenterOfRotationX = 0 };
var story = new Storyboard();
var animation = new DoubleAnimation { Duration = TimeSpan.FromSeconds(.35), To = to, From = from };
Storyboard.SetTargetProperty(animation, new PropertyPath("(UIElement.Projection).(PlaneProjection.RotationY)"));
Storyboard.SetTarget(animation, source);
story.Children.Add(animation);
await story.BeginAsync();
}
The code is very similar to the slide transition, and still very simple. Just as before, I am using Morten’s Storyboard as a Task extension method. Adding these animations to pages is the same as before. simply call the transition method on the object you want to animate.
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
try
{
await LayoutRoot.TransitionInTurnstile();
}
catch { }
}
Remember that we are performing an async operation within a void method, so wrap the call in a try/catch. This code shouldn’t error, but you never know.
When navigating backwards, first cancel the navigation, animate, and then navigate!
protected override async void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
base.OnBackKeyPress(e);
try
{
await LayoutRoot.TransitionOutTurnstile();
}
catch { }
NavigationService.GoBack();
}
I recently updated my app Santa Calls to include a settings page that would allow the user to delay a call or pin lock the application. When navigating to this page I wanted a nice transition, but I wanted the tree in the background to remain.
To accomplish this I set the background of these pages to be the tree image and moved the content in/out when needed. I would not be able to accomplish this type of transition using an SDK like the Windows Phone Toolkit or Telerik unless I set the application background to be the tree. I did not want to do this because the phone call pages do not have this same background.
The transitions are pretty simple and can be used by any page to move content up/down. I created a handy extension method that can be used by any FrameworkElement. To slide the content up into view, we need to create a Storyboard with a DoubleAnimation that will move the content (assumed to be a page) from the bottom of the page to the top.
public static async Task TransitionOutSlideDown(this FrameworkElement source)
{
await source.SlideVertically(0, 800);
}
public static async Task TransitionInSlideUp(this FrameworkElement source)
{
await source.SlideVertically(800, 0);
}
private static async Task SlideVertically(this FrameworkElement source, int from, int to)
{
if (source == null) return;
source.RenderTransform = new CompositeTransform();
var story = new Storyboard();
var animation = new DoubleAnimation { Duration = TimeSpan.FromSeconds(.2), To = to, From = from };
Storyboard.SetTargetProperty(animation, new PropertyPath("(UIElement.RenderTransform).(CompositeTransform.TranslateY)"));
Storyboard.SetTarget(animation, source);
story.Children.Add(animation);
await story.BeginAsync();
}
This uses a BeginAsync extension method from Morten Nielsen to run a Storyboard as a Task. To slide the content up or down we pass the from and to Y values to the SlideVertically method.
Now that we have our extension methods, we need to use them. When navigating to a page, override the OnNavigatedTo method and call the TransitionInSlideUp method.
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
try
{
await LayoutRoot.TransitionInSlideUp();
}
catch { }
}
The slide out transition is a little different. You already know how to exit app when awaiting an operation. This will be very similar. Instead of exiting the app, you’ll continue to navigate backwards.
protected override async void OnBackKeyPress(CancelEventArgs e)
{
e.Cancel = true;
base.OnBackKeyPress(e);
try
{
await LayoutRoot.TransitionOutSlideDown();
}
catch { }
NavigationService.GoBack();
}
With the SlideVertically method you can easily create slide updown transitions for navigating in or out.