Awaitable ManagedAnimation class for WinRT (Part 2)

In the first part of this post series, I discussed the reasoning behind potentially wanting to have a second way of doing animation in XAML.

In this post, I will discuss the basic infrastructure for this new way of doing thing animation.

The Orchestration class

In the root of the infrastructure reside two classes – the ManagedAnimation and Orchestration classes. Orchestration is a static helper class with some utilities pertaining mostly to how storage of the currently running animations. For each element that’s participating in this new system, it will store a list of currently active ManagedAnimation instances by way of an attached dependency property:

public class Orchestration
{
public static readonly DependencyProperty ManagedAnimationsProperty =
DependencyProperty.RegisterAttached("ManagedAnimations", typeof(List<ManagedAnimation>), typeof(Orchestration), new PropertyMetadata(null));


public static List<ManagedAnimation> GetManagedAnimations(DependencyObject obj)
{
return (List<ManagedAnimation>)obj.GetValue(ManagedAnimationsProperty);
}

public static void SetManagedAnimations(DependencyObject obj, List<ManagedAnimation> value)
{
obj.SetValue(ManagedAnimationsProperty, value);
}

 

That’s pretty simple. On top of that, we’ll add a couple of helper methods that will make working with this class easier:

First off, the VerifyList method which makes sure there’s a list of ManagedAnimation instances available on a given element:

private static List<ManagedAnimation> VerifyList(ManagedAnimation managedAnimation)
{
var list = GetManagedAnimations(managedAnimation.Element);
if (list == null)
{
SetManagedAnimations(managedAnimation.Element, list = new List<ManagedAnimation>());
}
return list;
}

The relevant DependencyObject is extracted from ManagedAnimation (we’ll see this a bit later) and the method makes sure there’s a list instantiated and available.

Next, we have two methods – one for adding and one for removing a ManagedAnimation to a DepenedencyObject (usually to a FrameworkElement):

internal static void AddManagedAnimation(ManagedAnimation managedAnimation)
{
var list = VerifyList(managedAnimation);
int index = list.IndexOfItem(x => x.Equivalent(managedAnimation));
if (index != -1)
{
var managed = list[index];
managed.Retain();
managed.Stop();
list.RemoveAt(index);
}

list.Add(managedAnimation);
}

internal static void RemoveManagedAnimation(ManagedAnimation managedAnimation)
{
var list = GetManagedAnimations(managedAnimation.Element);
if (list != null)
{
managedAnimation.Retain();
managedAnimation.Stop();
list.Remove(managedAnimation);
}
}

The functionality is fairly straightforward. The only interesting bit here is that before adding a ManagedAnimation instance from the list, the call makes sure an equivalent one is not already applied (for more information about Equivalent(), see below). The call also makes sure to call Retain() and Stop() to keep the values that are currently assigned and stop the currently running animation. If the animation was not stopped, the new Storyboard, when called to start, would throw (since you cannot animate the same property of the same object more than by one Storyboard at any given time).

That’s it. We now have a simple utility class that can keep track of the various animations currently running on a given element.

The ManagedAnimation class

This is where things start to get a little more interesting. The ManagedAnimation class is a base class used to apply animation to a given element. It contains the basic management routines and takes care of [most] of the legwork needed.

public abstract class ManagedAnimation
{
private Storyboard m_storyboard;
private ManagedAnimationCompleteEventArgs m_completeResult = new ManagedAnimationCompleteEventArgs() { NaturallyCompleted = true };

public bool ForceFinalValueRetained { get; set; }
public FrameworkElement Element { get; protected set; }
// ...
}

The class contains a handful of fields/properties:

  • m_storyboard: This class contains the animation definition applied by this ManagedAnimation instance. As you will see in a bit, the derived classes are the ones that are going to “fill” it up.
  • m_completeResult: This is the value returned when awaiting an animation. It contains some simple information such as whether or not the animation was completed naturally.
  • ForceFinalValueRetained: This property specifies whether the animation will “snap” to the final value specified when stopped before it’s complete, or if it’s going to stay where it is. For animation to look “continuous”, this will almost always be false. An example for when this should be true is if there’s an especially long running animation of Opacity and a second animation of Opacity may start at some point before the end. When that’s the case, the developer may pass true for this value, and the infrastructure will make sure Opacity goes to it’s final animation value before starting to animate again.
  • FrameworkElement: This is the element the animation will operate on.

Next comes the important bit of this class – the actual activation of the animation:

public async Task<ManagedAnimationCompleteEventArgs> Activate(FrameworkElement element, bool forceFinalValueRetained)
{
if (Element != null)
{
throw new InvalidOperationException();
}
ForceFinalValueRetained = forceFinalValueRetained;
Element = element;
m_storyboard = CreateStoryboard();
Orchestration.AddManagedAnimation(this);
await m_storyboard.BeginAsync();

m_storyboard = null;
Orchestration.RemoveManagedAnimation(this);
Element = null;

var result = m_completeResult;

return result;
}

The activation method sets the various parameters it gets on the class and then proceeds to start the animation. First, it creates the Storyboard by calling the abstract CreateStoryboard() method which will be implemented by the derived classes. It then calls the BeginAsync() extension method on the created Storyboard (more about that one at the end of this post) and awaits it.

This makes the animation sequence start.  When the animation is complete, the activate method cleans up after itself – it calls the Orchestration class to remove itself from the element, nullifies a bunch of fields/properties and returns.

protected abstract Storyboard CreateStoryboard();

public virtual bool Equivalent(ManagedAnimation managedAnimation)
{
return managedAnimation.GetType() == GetType();
}

public abstract void Retain();

The final three interesting methods in the class are:

  1.  CreateStoryboard() we touched on before – inheriting classes do the heavy-lifting there and build the relevant Storyboard.
  2. Equivalent() is used by the Orchestration class to figure out if one ManagedAnimation class is equivalent to another and as such needs to cancel an existing one.
  3. Retain()  is used at the end of an animation to “nail” the current state of the object so that it will remain there (since we are stopping the animation).

A note about Storyboard.BeginAsync()

BeginAsync() is an extension method available in the Extensions class in the library. It uses a fairly standard mechanism for transforming an event into an awaitable operation (an operation returning a Task instance).

public static Task BeginAsync(this Storyboard storyboard)
{
return EventToTaskAsync<object>(
e => { storyboard.Completed += e; storyboard.Begin(); },
e => storyboard.Completed -= e);
}

The code for EventToTaskAsync() is also available in the download (which will be available at the final post).

What’s next?

The next post in this series will discuss how to implement the OpacityManagedAnimation class – used to change opacity of objects. The post will also contain the first simple application that will make use of this animation.

This entry was posted in Animation, Dev, Windows8. Bookmark the permalink.

Leave a comment