Visually Located

XAML and GIS

Creating simple page transitions using Storyboards: Fly away transition

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.

blog comments powered by Disqus