Click here to Skip to main content
15,885,878 members
Articles / Mobile Apps / Windows Phone 7

Metro in Motion Part #3 – Flying Titles!

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
18 May 2011CPOL4 min read 14.6K   6  
In this blog post, I look at how to implement the fly-out fly-in effect seen in native Windows Phone 7 applications. This effect is seen in the native mail application; when you click on a message, the title flies out of the list then flies back in as the title of the message page.

This is the third in my “Metro in Motion” series where I am looking at how to re-create some of the stylish transitions and animations found in native Windows Phone 7 applications. As Silverlight developers, we have controls that adhere to the static Metro styling, but apart from Pivot and Panorama, the more dynamic features of the Metro style are something we have to come up with ourselves. So far, the previous posts have provided re-useable implementations for the fluid list animations between pivot pages and the ‘peel’ animation seen when applications exit. In this blog post, I provide a simple re-useable implementation of the title fly-out and fly-in effect. This effect is not that easy to describe, so we’ll let the following picture do the talking …

Image 1

Here is a video of this effect recorded on the emulator. This code has also been tested on a real device, and performs just fine: YouTube.

Unfortunately, because the animations are quite snappy, a YouTube video doesn’t do them justice. Best viewed on a real phone!

Let’s look at how this effect is implemented …

Starting with the fly-out effect, the general principle is that when the user selects an item in the list, we handle some event in the code-behind. We must identify the on-screen element that flies off screen and animate its location using a storyboard, whilst fading everything else from view. When this animation has completed, we navigate to the next page. When the user returns to the same page, this element must be animated to return to its original location.

In order to make the code re-useable, I have created a class which you present with the element to animate, it takes care of the fly-out effect, and then the fly-in which returns the UI to the original state.

Let’s take a look at the code …

Using this Effect

Firstly, we need to identify the elements which we wish to animate, so here we give them a name:

XML
<StackPanel Orientation="Vertical">
  <TextBlock Text="{Binding Title}"
          x:Name="Title"
          Style="{StaticResource PhoneTextLargeStyle}"/>
  <TextBlock Text="{Binding Summary}"
          Style="{StaticResource PhoneTextSmallStyle}"
          local:MetroInMotion.AnimationLevel="1"/>
  <TextBlock Text="{Binding Date}"
          Style="{StaticResource PhoneTextSmallStyle}"
          local:MetroInMotion.AnimationLevel="2"/>
</StackPanel>

The page needs an instance of the class which manages this animation effect. When the ListBox raises its SelectionChanged event, we use a bit of LINQ-to-VisualTree to locate the element named above, then invoke ItemFlyOut, providing an ‘action’ which is called when the animation completes.

C#
private ItemFlyInAndOutAnimations _flyOutAnimation = new ItemFlyInAndOutAnimations();

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  ListBox list = sender as ListBox;

  // find the selected ListBoxItem
  var selectedContainer = 
      list.ItemContainerGenerator.ContainerFromItem(list.SelectedItem);

  // grab the Title element
  var exitAnimationElement = selectedContainer.Descendants()
                                              .OfType<FrameworkElement>()
                                              .SingleOrDefault(el => el.Name == "Title");

  if (exitAnimationElement != null)
  {
    // animate the element, navigating to the new page when the animation finishes
    _flyOutAnimation.ItemFlyOut(exitAnimationElement, () =>
      {
        FrameworkElement root = Application.Current.RootVisual as FrameworkElement;
        root.DataContext = list.SelectedItem;
        NavigationService.Navigate(new Uri("/DetailsPage.xaml", UriKind.Relative));
      });
  }     
}

When the user navigates back to the page, you simple invoke ItemFlyIn, and this class takes care of animating the title element back into view:

C#
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
  // fly-in animation called via the dispatcher
  // in order to ensure the page is fully rendered.
  Dispatcher.BeginInvoke(() => _flyOutAnimation.ItemFlyIn() );
}

This class also has a static method which can be used to animate the page title into view. With the page title named in XAML, it is as easy to use as this …

C#
public partial class DetailsPage : PhoneApplicationPage
{
  public DetailsPage()
  {
    InitializeComponent();

    ItemFlyInAndOutAnimations.TitleFlyIn(PageTitle);
  }
}

So, this aptly named ItemFlyInAndOutAnimations does a good job of making this effect re-useable. Let’s look under the covers to see how it works …

How It Works

Probably the trickiest part of this effect for me was working out how to fade the page, via its opacity, without fading out the element being animated. The opacity property is inherited from an element to its children, therefore, if you set the opacity of the page, every element within it will have their opacity set also. To avoid this, the solution I came up with ‘clones’ the element that is being animated and places it in a popup; the popup also contains a full-screen rectangle which is used to fade-out the background.

Image 2

The class which performs the animation constructs a Popup and a Canvas which is the popup’s child element:

C#
/// <summary>
/// Animates an element so that it flies out and flies in!
/// </summary>
public class ItemFlyInAndOutAnimations
{
  private Popup _popup;
  private Canvas _popupCanvas;
  private FrameworkElement _targetElement;
  private Point _targetElementPosition;
  private Image _targetElementClone;
  private Rectangle _backgroundMask;
  private static TimeSpan _flyInSpeed = TimeSpan.FromMilliseconds(200);
  private static TimeSpan _flyOutSpeed = TimeSpan.FromMilliseconds(300);

  public ItemFlyInAndOutAnimations()
  {
    // construct a popup, with a Canvas as its child
    _popup = new Popup();
    _popupCanvas = new Canvas();
    _popup.Child = _popupCanvas;
  }

 ...
}

The fly-out animation creates a full-screen rectangle which masks everything on the screen, it then clones the visuals of the element being animated using a WriteableBitmap and places this in the popup also. A number of DoubleAnimations are constructed, one which animates the X location, one for the Y, and one which animates the rectangle opacity to gradually fade-out the background. Note, the curved path that the animated element follows is achieved by using a EaseOut on the Y animation, and an EaseIn on the X:

C#
/// <summary>
/// Animate the given element so that it flies off screen, fading 
/// everything else that is on screen.
/// </summary>
public void ItemFlyOut(FrameworkElement element, Action action)
{
  _targetElement = element;
  var rootElement = Application.Current.RootVisual as FrameworkElement;

  _backgroundMask = new Rectangle()
  {
    Fill = new SolidColorBrush(Colors.Black),
    Opacity = 0.0,
    Width = rootElement.ActualWidth,
    Height = rootElement.ActualHeight
  };
  _popupCanvas.Children.Add(_backgroundMask);

  _targetElementClone = new Image()
  {
    Source = new WriteableBitmap(element, null)
  };
  _popupCanvas.Children.Add(_targetElementClone);

  _targetElementPosition = element.GetRelativePosition(rootElement);
  Canvas.SetTop(_targetElementClone, _targetElementPosition.Y);
  Canvas.SetLeft(_targetElementClone, _targetElementPosition.X);

  var sb = new Storyboard();

  // animate the X position
  var db = CreateDoubleAnimation(_targetElementPosition.X, 
           _targetElementPosition.X + 500,
           new SineEase() { EasingMode = EasingMode.EaseIn },
           _targetElementClone, Canvas.LeftProperty, _flyOutSpeed);
  sb.Children.Add(db);

  // animate the Y position
  db = CreateDoubleAnimation(_targetElementPosition.Y, 
       _targetElementPosition.Y + 50,
       new SineEase() { EasingMode = EasingMode.EaseOut },
       _targetElementClone, Canvas.TopProperty, _flyOutSpeed);
  sb.Children.Add(db);     

  // fade out the other elements
  db = CreateDoubleAnimation(0, 1,
      null, _backgroundMask, UIElement.OpacityProperty, _flyOutSpeed);
  sb.Children.Add(db);

  sb.Completed += (s, e2) =>
    {
      action();

      // hide the popup, by placing a task on the dispatcher queue, this
      // should be executed after the navigation has occurred
      element.Dispatcher.BeginInvoke(() =>
      {
        _popup.IsOpen = false;
      });
    };

  // hide the element we have 'cloned' into the popup
  element.Opacity = 0.0;

  // open the popup
  _popup.IsOpen = true;

  // begin the animation
  sb.Begin();
}

private static DoubleAnimation CreateDoubleAnimation(double from, double to, 
        IEasingFunction easing, DependencyObject target, 
        object propertyPath, TimeSpan duration)
{
  var db = new DoubleAnimation();
  db.To = to;
  db.From = from;
  db.EasingFunction = easing;
  db.Duration = duration;
  Storyboard.SetTarget(db, target);
  Storyboard.SetTargetProperty(db, new PropertyPath(propertyPath));
  return db;
}

Animating the element back into view is a little easier, here we remove the background mask and animate our cloned element back to its original location. Once the animation has completed, we hide the popup and destroy our temporary visuals, and finally set the opacity of the original element back to '1.0', returning the UI to its original state:

C#
/// <summary>
/// Animate the previously 'flown-out' element back to its original location.
/// </summary>
public void ItemFlyIn()
{
  if (_popupCanvas.Children.Count != 2)
    return;

  _popup.IsOpen = true;
  _backgroundMask.Opacity = 0.0;

  Image animatedImage = _popupCanvas.Children[1] as Image;

  var sb = new Storyboard();

  // animate the X position
  var db = CreateDoubleAnimation(_targetElementPosition.X - 100, 
           _targetElementPosition.X, new SineEase(),
           _targetElementClone, Canvas.LeftProperty, _flyInSpeed);
  sb.Children.Add(db);

  // animate the Y position
  db = CreateDoubleAnimation(_targetElementPosition.Y - 50, 
       _targetElementPosition.Y, new SineEase(),
       _targetElementClone, Canvas.TopProperty, _flyInSpeed);
  sb.Children.Add(db);

  sb.Completed += (s, e) =>
    {
      // when the animation has finished, hide the popup once more
      _popup.IsOpen = false;

      // restore the element we have animated
      _targetElement.Opacity = 1.0;

      // and get rid of our clone
      _popupCanvas.Children.Clear();
    };

  sb.Begin();
}

And there we have it, Metro style flying titles!

You can download the full source code here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.

I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.

I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.

Visit my blog - Colin Eberhardt's Adventures in .NET.

Follow me on Twitter - @ColinEberhardt

-

Comments and Discussions

 
-- There are no messages in this forum --