22 July 2013

Extension methods to animate an object over the screen via waypoints for Windows Phone

A long time ago, before I even was a Microsoft MVP, I wrote an article about a behavior that could make anything draggable and flickable for Windows Phone (then still 7). That article used a few extension methods to FrameworkElement and Storyboard to create the desired effect Those extension methods are a bit limited, because they can only create an animation that move an object from one location to another – there are no possible points in between.

The Windows Phone 8 game I am currently developing needs just that, and as usual, as soon as I created something reusable, I spin it off to my library and start blogging about it. Don’t despair if you don’t get the whole explanation – I explain how I created the code and what it does, but if you don’t care, you can just copy it and use the resulting calls.

The earlier extension methods created a DoubleAnimation. Well actually it created two – one to animate the TranslateX property of a CompositeTransform, the other to animate the TranslateY property, over a certain Duration. But these kinds of ‘simple’ animations are executed in parallel in a Storyboard – and now I want to do consecutive animations. You then need key frames. And since the Translate properties are doubles, you need to create instances of DoubleAnimationUsingKeyFrames :). Luckily, the Microsoft developers creating this API did choose some logical names. There are a few things you should remember:

  • The DoubleAnimationUsingKeyFrames animates the property, so this needs to be used in the SetTarget of the Storyboard
  • For the individual animation frames you need to add LinearDoubleKeyFrame instances to the DoubleAnimationUsingKeyFrames’ KeyFrames collection – with a key time and a value. This is the value which the animated property needs to have at the key time.
  • The duration of the entire DoubleAnimationUsingKeyFrames needs to be equal to the sum of key times of the individual LinearDoubleKeyFrame key times.
  • We need to animate both X and Y, so there needs to be two animations per key time, namely one for X and one for Y

Let’s do that!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Wp7nl.Utilities
{
  public static class StoryboardExtensions2
  {
    public static Timeline CreateKeyFrameAnimation(this Storyboard storyboard, 
        IList<double> values, IList<Duration>times)
    {
      var keyFrameAnimation = new DoubleAnimationUsingKeyFrames();
      var keyTime = TimeSpan.FromMilliseconds(0);
      for (var i = 0 ; i< values.Count(); i++)
      {
        keyTime += times[i].TimeSpan;
        var frame = new LinearDoubleKeyFrame {Value = values[i], KeyTime = keyTime};
        keyFrameAnimation.KeyFrames.Add(frame);
        keyFrameAnimation.Duration += times[i];
      }
      return keyFrameAnimation;
    }
  }
}

This code is still pretty abstract, but it’s doing exactly what I described – it makes the DoubleAnimationUsingKeyFrames, adds the keyframes (of type LinearDoubleKeyFrame  - I found that out using Blend and basically translated the XAML to C#) and makes the total duration equal to the sum of the durations.The first key frame on key time 0 is the first point – and from then we add more points and new key times.

This, of course, is not very “programmer friendly”. That is where the main extension method comes in:

public static void AddWayPointAnimation(
  this Storyboard storyboard, FrameworkElement fe, 
  IList<Point> points, double speed)
{
  var durations = new List<Duration> {new Duration(TimeSpan.FromSeconds(0))};
  for (var i = 0; i < points.Count - 1; i++)
  {
    durations.Add(points[i].CalculateDuration(points[i + 1], speed));
  }
  var xValues = points.Select(p => p.X).ToList();
  storyboard.AddAnimation(fe.RenderTransform, 
    storyboard.CreateKeyFrameAnimation(xValues, durations), 
       CompositeTransform.TranslateXProperty);
  var yValues = points.Select(p => p.Y).ToList();
  storyboard.AddAnimation(fe.RenderTransform, 
    storyboard.CreateKeyFrameAnimation(yValues, durations), 
      CompositeTransform.TranslateYProperty);
}

So you pass in an empty Storyboard, the GUI element you want to animate, a list of waypoints, and a speed (in pixels per second). And done. You can now simply use the following code:

myGuiElement.RenderTransform = new CompositeTransform();
myGuiElement.RenderTransformOrigin = new Point(0.5, 0.5);
var s = new Storyboard();
s.AddWayPointAnimation(myGuiElement, myListOfPoint, 500);
s.Begin();

And whatever is in myGuiElement will be animated over the waypoints. The first two statements can also be replaced by creating the transform in Blend. You may, by the way, have noticed some odd statements in AddWayPointAnimation. Both the red underlined statements are not part of the standard API. CalculateDuration is part of a few extension methods for Point that I wrote some time ago, and AddAnimation is part of the original article I mentioned before. Both are now part of my #wp7nl library on codeplex (and its accompanying NuGet package) so I won’t go into details about this.

screenshot4To demonstrate this principle I have made the following pinnacle of animation design:

When you hit the button “move”it will move the whole TitlePanel first a bit to the left, then down, then to the right, and finally up to where it came from. Pretty pointless to do this from code, but it proves the point.

Note that what we animate here is a Translation. So the coordinates used are relative coordinates within the container!

As usual, the full demo solution can be downloaded here

2 comments:

Dan Diaconu said...

Hi, very nice post, especially for understanding how animations work.

For Animations in Silverlight and Windows Phone I recommend the Artefact Animator Framework http://artefactanimator.codeplex.com/
I have use this very extensively in my WP app (PicPoint)

Joost van Schaik said...

@Dan thanks. But that's for WPF and Silverlight 4 and my the look of it, long time not updated.