Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / WPF

Fluent and Imperative Animations

Rate me:
Please Sign up or sign in to vote.
4.96/5 (85 votes)
18 Jun 2016Apache26 min read 104.7K   4.8K   115   51
Create animations easily using a Fluent API that integrates well with frame-based animation segments.

Old Links - Not Sure if Any of Them Work

See the Cyberminds57 animation - External Source and requires Silverlight 5 

See a Shoot'em Up game made with this library - External Source and requires Silverlight 5. You can see a tutorial on how to use this library to write a game at Shoot'em Up .NET[^].

You can also contribute to this library using CodePlex.

Example

C#
AnimationBuilder.
  BeginSequence().

    BeginParallel().
      Walk(pfzCharacter, LookDirection.Down, 200).
      Walk(sboCharacter, LookDirection.Down, 220).
    EndParallel().

    Say(1.5, "Hi, my name is Paulo Zemek.", HorizontalAlignment.Right, pfzCharacter).
    Say(1.5, "Hi, my name is Sébastien Boudreau.", HorizontalAlignment.Left, sboCharacter).
    Say(1.5, "We are aliens.", HorizontalAlignment.Center, pfzCharacter, sboCharacter).

    BeginParallel().
      Walk(pfzCharacter, LookDirection.Left, -pfzCharacter.Width).
      Walk(sboCharacter, LookDirection.Right, canvas.ActualWidth).
    EndParallel().

    Add(() => { pfzCharacter.Top = 500; pfzCharacter.Left = 200; }).
    Walk(pfzCharacter, LookDirection.Up, 300).
    Say(3, "Humm... it seems I teleported myself again.\r\nWhy was I walking after all?", HorizontalAlignment.Right, pfzCharacter).
    Add(_RandomMoves(pfzCharacter)). // Random moves is frame-based.
    Range(1.0, 0.0, 1, (value) => pfzCharacter.Opacity = value).

  EndSequence();

Click here to see that particular animation running (Requires Silverlight 5).

Or click on the next image to see a more complete example (which is the same found in the download sample [it requires Silverlight 5 too]):

Image 1

Image 2Introduction

After writing the article Fluent Method and Type Builder I got myself thinking about games and animations, and I was thinking if it was possible to create something to integrate frame-based and time-based animations in an easy to use API.

I must say that for complex animations I definitely prefer the frame-based ones, as at each frame I can check states and decide what is the "next step". I don't need to deal with hard calculations to decide what to do if an entire second passed as I can execute frame-by-frame in memory until I am at the right frame. This greatly simplifies code but there are situations that time-based animations are much better (as I can have any number of "intermediate frames" with a single calculation) but there is where I have problems to integrate both animation kinds.

I originally though about creating something to make it easier to use frame-based animations inside WPF's Storyboards, but I ended-up doing something completely different, which I consider much more natural to how I see animations.

In fact, I see the following problems with animations in WPF:

  • They are WPF specific. Yeah, I was talking about a WPF's Storyboard, but what if I only want to use the animations capabilities in a Windows Forms application or even in a server application that is "dealing with the animations" but is sending the results to the clients via TCP?
  • They look too verbose to me. I prefer simple animations to be one-liners if possible.
  • I consider it terrible that each animation needs to have a start-time to start just after another one. That forces developers to rewrite all the start times if a previous animation should take longer. Also, all the durations must be known when the animation is created, which can be impossible to tell if we have animations that wait on user inputs as part of the sequence.
  • The Timeline and AnimationTimeline classes already have too many responsibilities, which make them hard to be inherited and implemented properly;
  • The AnimationTimeline expects to generate values to affect a single property (which must be a DependencyProperty).

What I wanted (and also what I will be presenting in this article) is an animation framework:

  • Capable of animating frame-based and time-based animations;
  • Capable of telling which animations run in parallel and which animations runs as a sequence, so you don't need to know the start time of each animation;
  • Capable of mixing frame-based and time-based animations (note that this is different than simply supporting both kinds);
  • Capable of working in an imperative or in a declarative way (and also mixing them);
  • Capable of animating as many properties as needed with a single animation;
  • Usable by other types of application (like server applications, Windows Form applications etc);
  • Easily expandable by implementing a single interface (the IAnimation);
  • Capable of changing some behaviors by "decorating" animations with other animations, like loops, speed modifiers and conditions.

Image 3Declarative and Imperative Animations

If the terms declarative and imperative aren't clear enough, I will show the difference by examples:

Declarative

C#
AnimationBuilder.
  BeginLoop().
    BeginSequence().
      Range(20, 100, 1, (value) => button.Height = value).
      Range(100, 20, 1, (value) => button.Height = value).
    EndSequence().
  EndLoop();

Imperative

C#
IEnumerable<IAnimation> _Animation()
{
  while(true)
  {
    yield return Range.Create(20, 100, 1, (value) => button.Height = value);
    yield return Range.Create(100, 20, 1, (value) => button.Height = value);
  }
}

The Fluent API is the declarative part of the library. In it we tell the entire "animation flux" but we don't really control the execution of the animation by individual instructions. We are of course using instructions to create the declarative animation, but the animation itself is no more controlled by our instructions. Some people consider this better as we can easily see the entire animation flux, but at the same time this is more limited, as such flux should be known before starting the animation.

On the other hand, the Imperative way will execute your code to know what to do next. This opens the possibility to use any programming instruction you need, so you can check different states, do really complex calculations and then return the appropriate animation part, or you can even make the animation work frame-by-frame (so instead of returning the next animation, you do what you need to do and return a WaitNextFrame()).

So, the previous example can be frame-based if we wrote it like this:

C#
IEnumerable<IAnimation> _Animation()
{
  var helper = FrameBasedAnimation.CreateHelper(80); 
  // this helper will keep the animation at 80 frames per second.

  while(true)
  {
    for(int i=20; i<100; i++)
    {
      button.Height = value;
      yield return helper.WaitNextFrame();
    }

    yield return Range.Create(100, 20, 1, (value) => button.Height = value);
    // Yeah, I am mixing the modes here. The first part of the animation is 
    // frame-based and the second part uses the Range which is time based.
    // But it is not hard to imagine that we can copy the first for and
    // make it work from 100 to 20.
  }
}

Image 4Fluent Animation Builder API - Basic

To understand this library I will start by the Fluent API. It is the basic part and the one that you will use to create many different and non-interactive animations. Even if later you decide to create frame-based animations you can still count on the Fluent API to create smaller sequences that are the result of the user actions, so I don't see a reason not to start by it.

The heart of the Fluent API is the AnimationBuilder class. With it you can create any kind of "animation storyboard".

The principles here are simple:

  • All the methods that create an animation fragment that supports inner animation fragments start by a Begin and are followed by the animation name and you finish such fragment by a call to EndAnimationName;
  • Inside any fragment that supports inner fragments you can call Add() to give a fragment created by hand or to give an Imperative animation;
  • At any moment you can call Range to create an animation that goes from one value to another;
  • The animation fragments that support inner fragments can be either decorators (they somewhat change the behavior of a single inner animation) or they can be animation groups (at this moment there are only two, one that runs animations in parallel and one that run animations as a sequence).

Even if the decorators only support a single animation inside them, you can always create a parallel or sequential group as their content, so you can have many animations running inside a fragment that initially only supports one fragment.

Are you confused by those terms (like decorators)? OK, so let's see the basic example of declarative animations again:

C#
AnimationBuilder.
  BeginLoop().
    BeginSequence().
      Range(20, 100, 1, (value) => button.Height = value).
      Range(100, 20, 1, (value) => button.Height = value).
    EndSequence().
  EndLoop();

The BeginLoop() is creating a Loop decorator. The Loop, as the name says, will Loop (or repeat, if you prefer) its single inner animation.

But as you can see, its single animation is a sequence (created by the BeginSequence call) and inside such sequence we have two Range calls.

If you analyse this in the inverse order, we can say that:

  • We have a fragment that will make the button's Height bigger (from height 20 to 100) in one second;
  • We have a fragment that will make the button's Height smaller;
  • Then we say they will run as a sequence (so one after the other);
  • And finally, that such sequence will run as an infinite loop.

Method Summary

I think the previous examples are enough to get an idea. Now I will present all the methods that come with the library and explain what they do.

I will not give really complex examples here, but if you download the sample you can see many different animations and their code.

Method Name Sample usage + Description
Range
C#
Range(initialValue, finalValue, durationInSeconds, (value) => character.Left = value);

The Range is probably the most important fragment that you will use. It is the responsible for making some value change based on the elapsed time, so it is the one that will make a color gradually change from red to green in one second, to move objects on the screen from left to right and even to gradually make an object less transparent. Even being extremely simple, it is the one that is capable of really showing something happening.

Image 5

The animation presented here only tries to show what will happen if two objects are animated with the same duration, but different final values. It is useful to compare to the next item.

RangeBySpeed
C#
RangeBySpeed(initialValue, finalValue, speed, (value) => character.Left = value);
// or
RangeBySpeed(() => character.Left, finalValue, speed, (value) => character.Left = value);

This is also a Range, like the previous one, but in this case you know at which speed the animation moves, not exactly how much time it will take. For example, you may want a character to go off the screen to the right from its actual position, but it should always "walk" at the same speed. So, if it is already at the right part of the screen it may take less than one second, but if it is at the left it may take 3 seconds. In this case how much time it takes will be calculated for you, you only need to know the speed (in general, this is the amount that will be changed in one second so, if we talk from a character moving to the right, the speed is how many pixels it will move per second).

The second version, which receives a delegate to read the actual value is useful if you don't know the initial value when declaring the animation (in the example, the character.Left may be at position 0 when declaring the animation but after executing a player controlled animation it may be anywhere). So you give a delegate to read the actual position when the animation starts.

Image 6

The animation presented here tries to show how two objects with the same speed but different final values will appear by using the RangeBySpeed method.

BeginRanges / EndRanges
C#
BeginRanges((value) => circle.Fill = new SolidColorBrush(value), Colors.Black).
  To(Colors.Blue, durationInSeconds).
  To(Colors.Red, 1).
  To(Colors.Green, 1).
  To(Colors.Yellow, 1).
  To(Colors.Magenta, 1).
  Wait(1).
  To(Colors.Black, 1).
EndRanges();

The BeginRanges/EndRanges is a special kind of sequence prepared to create many ranges that will all execute the same delegate to update the value. So, during the BeginRanges you give the delegate that will be executed and an initial value. Then, you call To() giving the next value to go to and how much time it will take to achive that value. As a sequence, you can use Wait() calls, so the animation can go from one value to another, wait a little at that value, and then go to another value.

Image 7

Add
C#
someAnimationContainer.
  Add(animation);

The Add() method only exists when you are actually inside another animation container. It will simply add the given animation to the animation container. This is useful if you create your own animation fragments or if you have a special animation that should be played in different parts of the same sequence, so you can first prepare such animation, and then Add() it where needed.

BeginLoop / EndLoop
C#
BeginLoop(5).
  Range(0, 100, 1, (value) => button.Left = value).
EndLoop();

The BeginLoop can receive a parameter to tell how many times its inner animation should be repeated. If you pass -1 or if you don't pass such parameter the loop will be infinite.

BeginParallel / EndParallel
C#
BeginParallel().
  Range(0, 500, 10, (value) => item.Left = value).
  Range(Colors.Black, Colors.White, 12, (value) => item.Color = value).
  Add(someOtherAnimation).
EndParallel();

Inside a parallel block all the added animations will run, well, in parallel. It is important to note that the parallel animation fragment itself will only be considered to end when the most long living inner animation ends (if it ends at all).

Image 8

The previous animation is only showing two ranges in parallel. It is not really showing the presented code. Yet, even the Range animations are showing two different ranges running in parallel, which requires the use of this command.

BeginSequence / EndSequence
C#
BeginSequence().
  Add(someAnimation).
  Add(someOtherAnimation).
EndSequence();

In my opinion the sequence is the most natural animation group. You know that you want to do many things, one after the other. You don't care (or you don't know) how much time each "thing" will take, but there is no problem, the sequence simply tells that one "thing" (animation fragment) starts after the other.

Image 9

The previous animation shows how two ranges will be played in a sequence. The sample only intends to show how the second "part" starts after the first is finished.

BeginAcceleratingStart / EndAcceleratingStart
C#
BeginAcceleratingStart(1).
  Range(0, 450, 3, (value) => Canvas.SetLeft(circle, value)).
EndAcceleratingStart();

There are many situations in which an animation may need to start slowly and then achieve its normal speed. Surely you can keep the time unchanged and accelerate the inner animation. But to simplify many of those situations in which you may want an "accelerating start" you can use the BeginAcceleratingStart decorator.

Such decorator will "lie" to the inner animation, saying that less time was elapsed than it really was in the beginning, accelerating in a progressive manner until the normal speed is achieved. The parameter it receives is how much time it will take to be at normal speed, but that's the "inner time", which means that such time is the time already affected by such acceleration. It is done this way so if you can ask for an accelerating start of one second and put an inner animation of only 1 second. In this case, it will end when it achieves full speed (if the outer time was used, the acceleration will finish in one second but, as the inner animation started slower, it will end later).

Image 10

BeginDeacceleratingEnd / EndDeacceleratingEnd
C#
BeginDeacceleratingEnd(1, 3).
  Range(0, 450, 3, (value) => Canvas.SetLeft(circle, value)).
EndDeacceleratingEnd();

This effect is the opposite of the previous one and it will make the ending of an animation to deaccelerate. If the inner animation is bigger than the part that should be deaccelerated you should give the entire animation duration as the second parameter. Unfortunately, in this case, you should know how much time the animation will take to tell where it should start deaccelerating.

Image 11

BeginTimeMultiplier / EndTimeMultiplier
C#
BeginTimeMultiplier(2).
  Range(0, 450, 3, (value) => Canvas.SetLeft(circle, value)).
EndTimeMultiplier();

In this example, we can say that such Range will end in 1.5 seconds because the time multiplier will simply "lie" the elapsed time to its inner animation, making it run at double speed. Of course this is not a practical use of the time multiplier, as it is better to give the right time to the range, but the TimeMultiplier can be used to affect the speed of any inner animation, which can be extremely complex.

BeginProgressiveTimeMultiplier / EndProgressiveTimeMultiplier
C#
BeginProgressiveTimeMultiplier(1, 0.2, 1.5).
  Add(someAnimation).
EndProgressiveTimeMultiplier();

The ProgressiveTimeMultiplier is the resource used by the AcceleratingStart and DeacceleratingEnd animations. When constructing it you tell how much time (in this case, 1 second) it will take to go from an initial speed (0.2) to a final speed (1.5). After the final speed is achieved, the rest of the animation is played using such final speed multiplier.

BeginRunCondition / EndRunCondition
C#
BeginRunCondition(() => checkBoxRun.IsChecked).
  Add(someAnimation).
EndRunCondition();

The RunCondition is a decorator that will only start its inner animation if a condition is met. However, if the condition changes after the animation started, it will continue to run until its end.

BeginPrematureEndCondition / EndPrematureEndCondition
C#
BeginPrematureEndCondition(() => !walking).
  BeginLoop().
    Range(0, character.FrameCount - 0.001, 0.4, (value) => character.FrameIndex = (int)value).
  EndLoop().
EndPrematureEndCondition();

The PrematureEndCondition is similar to the RunCondition but it has two main differences:

  1. Its condition must be false to allow the animation to run, as a value of true means it should end the animation;
  2. As soon as the value becomes true it will stop its inner animation. It will not try to let it run until the end as the RunCondition does.
BeginPauseCondition / EndPauseCondition
C#
BeginPauseCondition(() => checkBoxPaused.IsChecked).
  BeginLoop().
    Range(0, character.FrameCount - 0.001, 0.4, (value) => character.FrameIndex = (int)value).
  EndLoop().
EndPauseCondition();

The PauseCondition is a different condition than the previous ones in the sense that it will not decide if the animation starts or ends prematurely. It will simply avoid running its inner animation while the condition is true. By doing this, it allows inner animations to be paused, independently if those inner animations run frame-by-frame or are declarative ones.

Probably this one is the most useful one for frame-based animations, as it makes it extremely simple to add support for pauses without requiring to verify for a pause condition at each frame and so, it is very useful for games.

BeginTimeLimit / EndTimeLimit
C#
BeginTimeLimit(5).
  Add(animationWithUnknownDuration).
EndTimeLimit();

This decorator will limit the time an inner animation may run. This is internally used on some constructs that have many different decorators in a sequence, all of them working over the same animation, so each one of them is limited to a certain time. But it may be directly useful if you simply want to present "some seconds" of a game level, for example.

BeginDisposeOnEnd / EndDisposeOnEnd
C#
BeginDisposeOnEnd().
  Add(animationThatConsumesLotOfResources).
EndDisposeOnEnd();

Usually inner animations are kept in memory even when they are ended. This is useful if you put them inside a loop, so they are reset and run again. But if you are creating an animation that uses a lot of resources and is not going to be reset, you can put it inside a DisposeOnEnd block, so the animation will be disposed as soon as it ends.

Wait
C#
sequence.Wait(5);

The Wait() method only exists inside sequences (it will be useless to Wait() in parallel) and, as the name says, it simply means the sequence will wait some time before starting the next animation fragment.

BeginSegmentedTime / EndSegmentedTime
C#
BeginSegmentedTime(TimeSpan.FromMilliseconds(15), collisionDetectionDelegate).
  Add(animationToBeSegmented).
EndSegmentedTime();

This method is especially useful if you are animating your objects using time-based segments (like Range) but you need to check for collisions in a consistent manner.

So, to do it, you tell at which intervals the segments should be tested (usually used for collision detection). Each inner animation will run up to that segment size, execute the given delegate and continue to advance until the real time elapsed is reached. In case of smaller values, the inner animations are processed to generate smooth results.

Image 12Imperative Animations - Intermediary

Declarative animations must know everything they will do from the start and, even if they can have some interactions through the use of the RunCondition and Add calls that may execute any code, they are still limited. Their purpose is not to be interactive.

Imperative animations, on the other hand, can use any compiler constructs, like whiles, ifs, and even try/catch/finally blocks and, which is better, they can yield return other animations to run, like declarative ones, so you can create the complex logic in the imperative animation and then yield return the easier fragments that you can create with the Declarative API.

I already presented the imperative animations before, but let's see them again:

C#
IEnumerable<IAnimation> ImperativeAnimation()
{
  while(true)
  {
    yield return AnimationBuilder.Range(20, 100, 1, (value) => button.Height = value);
    yield return AnimationBuilder.Range(100, 20, 1, (value) => button.Height = value);
  }
}

As you can see, the "heart" of the imperative animations is the use of the yield return keyword to give the next animation to be executed. But, what if your code is already updating the objects it needs to update and you only want to wait for the next frame? I initially though about accepting yield return null; as the WaitNextFrame() instruction, but then I can't guarantee that all animations will have the same frame-rate.

So, to solve the problem I created the FrameBasedAnimationHelper and, as you can imagine by the name, it is a helper class with the purpose of making frame-based animations. When you create it you should tell the frame-rate of the animation and when you need you can use yield return helper.WaitNextFrame() or even yield return helper.Wait(someValue).

As it is created as a normal instance instead of being static you can have many animations running in parallel, but with different frame rates. It is not important how often WPF, Silverlight or Windows Forms is triggering the animation, such helper will keep your animation at the right frame-rate, compensating lost frames and waiting when the calls come too near each other.

C#
private static IEnumerable<IAnimation> _RandomMoves(ICharacterImage character)
{
  var random = new Random();
  var helper = FrameBasedAnimationHelper.CreateByFps(60);
  for (int i = 0; i < 60*4; i++)
  {
    double value = (i / 60.0) + 1;

    int x = random.Next(2);
    int y = random.Next(2);

    if (x == 0)
      x = -1;

    if (y == 0)
      y = -1;

    character.Left += x * value;
    character.Top += y * value;

    yield return helper.WaitNextFrame();
  }
}

ImperativeAnimation.AddSubordinatedParallel

You probably understood that the yield return of the ImperativeAnimations will work as a sequence. But what if you want to add an animation to run in parallel?

The normal answer is to add such animation to the AnimationManager directly. The animation manager simply runs all added animations in parallel. But in such case the animations will be completely independent. For example, if the actual animation is running inside a TimeMultiplier the new animation will not be affected by it. If the actual animation ends, the other animation will continue to run.

If that's not what you want, there is an alternative. Inside an imperative animation you can call ImperativeAnimation.AddSubordinatedParallel(). Such method will add an animation to run in parallel to the actual imperative animation, inside the same container. That is, if the imperative animation is inside a TimeMultiplier, such animation will also be inside a TimeMultiplier. Also, as a subordinated animation, when the imperative animation ends such parallel animation will end too. That is: It will run in parallel, with the same decorators but it is still considered a "child" animation that will only be alive while the main animation is alive.

I wanted to give a method summary of the imperative animations but, as they can use any C# construct, it will be of no use. So my summary is:

  • Using yield return is great to give new animations to run "on the fly";
  • If you want frame-based animations create a FrameBasedAnimationHelper and call yield return helper.WaitNextFrame();
  • If instead of yield returning an animation you want to initialize an animation that will run in parallel to the actual one, use the ImperativeAnimation.AddSubordinatedParallel().

Image 13Extending the Library - Advanced

I created this library with extensibility in mind and there are many extension points in this library: You can create new RangeCalculators (I give range calculators for int, double, Point and Color, but what if you want a range calculator for Rectangles or other numeric types?), you can create new SpeedToTimeCalculators (that's how the RangeBySpeed works, it will first calculate the time, then it will be a normal Range), you can create new animation fragments by implementing the IAnimation interface or by creating imperative ones.

But if you see the first example in the article, I am using the methods Walk() and Say() which aren't part of this library. Unfortunately it is not really possible to extend the AnimationBuilder type without changing its source-code, but we can create extension methods for the Fluent API. That's a solution that I don't really consider perfect, but the entire Fluent API is only there to make you able to write code that looks declarative, so for that purpose, don't be afraid of creating extension methods.

So, let's explore how to extend this library in many different ways:

Creating new RangeCalculators

The Range is probably the animation fragment that you will use the most, but this library only comes with support for int, double, Color and Point. If you want a range from any other type you will need to create your own range calculator. The easiest way to create one is to inherit from the RangeCalculator<T>, where the T is the type of the data to which you want to calculate the range. By inheriting such class you need only to implement the Calculate method, which receives the initialValue, the finalValue, the actualTime and the duration (or the totalTime). For most types, the method will look like this:

C#
TimeSpan remaining = duration-actualTime;
return (initialValue * remaining.TotalSeconds + finalValue * actualTime.TotalSeconds) / duration.TotalSeconds;

I say that it looks like this because you will probably need casts, not all types can be simply multiplied by a double (on colors, for example, each element must be processed independently) and you may very well want to create a different calculator that is not "linear".

Well, after creating your RangeCalculator you can give it as a parameter to the Range or you can register your range calculator as the default one for its type. Something like:

C#
RangeCalculator<MyType>.Default = new MyTypeRangeCalculator();

For example, this is the code to calculate the range between two points:

C#
public sealed class PointRangeCalculator:
  RangeCalculator<Point>
{
  public override Point Calculate(Point initialValue, Point finalValue, TimeSpan actualTime, TimeSpan duration)
  {
    double initialMultiplier = (duration - actualTime).TotalSeconds;
    double finalMultiplier = actualTime.TotalSeconds;
    double totalDivider = duration.TotalSeconds;

    double x = (((initialValue.X * initialMultiplier) + (finalValue.X * finalMultiplier)) / totalDivider);
    double y = (((initialValue.Y * initialMultiplier) + (finalValue.Y * finalMultiplier)) / totalDivider);
    return new Point(x, y);
  }
}

Note: This is the minimum required code to generate such calculator. If you see the source code you will notice that I created a private constructor and I also declared an Instance property. I did it that way because there is no need to create more than one instance of such range calculator, yet if you don't do that your code will continue to work and if you only create a single instance and register it as the default one you will not use more memory than necessary.

Creating new SpeedToTimeCalculators

The RangeBySpeed does not use a completely different logic to calculate the range. It simply calculates the duration of a given range by its speed and then calls a normal Range.

But to calculate the duration based on a speed information it is also necessary to find that calculator and, again, there are only some types that have calculators (int, double, Color and Point). If you want to support other types, you should create a SpeedToDurationCalculator.

To do it, inherit from the SpeedToDurationCalculator<T> and implement the CalculateDuration method. It receives the initialValue, the finalValue and the speed. It is important to know that even if the finalValue is less than the initialValue, the result should not be negative (also the received speed should never be less than or equal to zero).

The speed represents how much the value will change in one second. So, for a Color, it is how much the element with greater difference will change in one second (the other elements don't interfere in the speed as they are a proportion of the biggest change).

So, using the color as example, this is the code to create such SpeedToDurationCalculator:

C#
public sealed class ColorSpeedToDurationCalculator:
  SpeedToDurationCalculator<Color>
{
  public override TimeSpan CalculateDuration(Color initialValue, Color finalValue, double speed)
  {
    double a = Math.Abs(finalValue.A - initialValue.A);
    double r = Math.Abs(finalValue.R - initialValue.R);
    double g = Math.Abs(finalValue.G - initialValue.G);
    double b = Math.Abs(finalValue.B - initialValue.B);

    double maxValue = Math.Max(a, Math.Max(r, Math.Max(g, b)));
    return TimeSpan.FromSeconds(maxValue / speed);
  }
}

Implementing the IAnimation interface

The IAnimation interface has the following members:

IsUseless, Reset, Update and it is also disposable, so it has the Dispose method. So, to implement it you should understand each one of those members.

The IsUseless property should return true if the animation was disposed or if it is not functional. For example, a decorator or a group that does not have an inner animation is useless. This value helps the outer containers, which can then remove such useless animation.

The Reset method should clean up any used resources related to the actual position of the animation and also reposition it at the beginning. In normal situations, a call to Reset should not make the object useless and it is used by the the Loop so the animation can play again and again.

The Dispose, well, it should release all used resources and also make the animation useless. Note that different than what it is usually expected, Dispose is usually not called on animations. It is useful if you want to stop the animation in the middle. If the animation ends normally it should be able to be reset.

And finally, there is the Update. It is expected that it will only be called with positive elapsed times. In the Update you should do everything the animation is supposed to do considering the elapsed time and return true if the animation should play again or false if it ended. In normal situations ending the animation should not dispose it, as it is possible that such animation will be Reset and played again.

IAfterEndAwareAnimation

Suppose your animation is part of a sequence. It will live for more 100 milliseconds, but there is a slowdown and the next update come only after 500 milliseconds. Surely your animation will end, but if there was not such slowdown, the next animation will have started 400 milliseconds ago. Should you ignore that, and the next animation starts from zero, or should the next animation advance those 400 milliseconds?

If you can precisely calculate when the animation ended, then implement the IAfterEndAwareAnimation. Its only purpose is to tell how much time elapsed after the end of the animation and, in a sequence, will help correct slowdowns (or simply animations that ended in the middle) and this will avoid noticing "small pauses" from one animation fragment to the other.

Creating new decorators

Creating new IAnimation implementations may be useful if you want a different kind of animation that is not possible with the use of the Ranges or imperative animations. But maybe you only want to alter existing animations.

Well, there is not much that you can do to alter existing animations (you can only intercept the calls to Reset, Update, IsUseless and Dispose) and many of the existing behavior changes are already implemented. But, if you have an idea of a behavior change that is not already done in the framework, you can inherit the AnimationDecorator type.

In it you are expected to implement the Update method, as this is the method that you will usually intercept, but you are free to override the other methods. As a decorator, it has the InnerAnimation property. For example, the TimeMultiplier simply multiplies the elapsed time before giving it to its inner animation and the PrematureEndCondition will verify the condition and, if it is true, will return that the animation ended without calling its inner animation.

"Adding" methods to the Fluent API

Without changing the AnimationBuilder unit it is not possible to simply add new methods to it and at least at this moment there aren't ways to add static extension methods, so if what you want is to add a new method that's available directly by the AnimationBuilder static type that's not possible.

But if what you want is to be able to "add" new methods to the containers (so, you already did something like AnimationBuilder.BeginSequence().) you can use extension methods, but then there is another problem, the signature of the Fluent API methods is very problematic.

The solution is to create a generic extension method where the T is IAnimationBuilder. All the AnimationBuilders implement such interface, so such method will be added to all of them and, by being generic, you can return to the correct type. So, to add a normal method (that is, not a Begin method), you can use something like:

C#
public static T MethodName<T>(this T animationBuilder, ... any parameters your method needs ...)
where
  T: IAnimationBuilder
{
  animationBuilder.Add(someAnimationThatShouldBeCreated);
  return animationBuilder;
}

But if you want to create a Begin/End extension method pair you will need a little extra work. You should create a builder that inherits from the AnimationBuilder<TParent, TThis> but keeps the TParent as a generic parameter. Then you should create a constructor that receives the parent and the real animation fragment and you should also add an EndName that will return the Parent. You also need to implement the Add method (which will usually fill the InnerAnimation or Add the given animation to your own "animation" container).

Then, the extension method should be like this:

C#
public static NameBuilder<T> Name<T>(this T parent, ... parameters ...)
where
  T: IAnimationBuilder
{
  var animation = new NameAnimation(... parameters ...);
  parent.Add(animation);
  return new NameBuilder<T>(parent, animation);
}

Samples

Image 14 Image 15 Image 16

The samples in the download include three different applications:

  • The Windows Forms application is only there to show that the library can be used in Windows Forms, but it is far from an useful sample.
  • The WpfSample is the sample that works better as a tutorial. It has different kind of animations with different complexities.
  • Finally there is the Silverlight application, which is a more complete utilisation of the library and also the beginning of our personal presentation.

Image 17External Files

The background image used on the actual Cyberminds57 animation was found in the site http://besthdwallpapersdesktop.com/designer-room/ and is not subject to the this article's license.

The sounds used in Cyberminds57 were found in http://www.freesfx.co.uk/[^].

I don't remember where I got the soldier character, but I really believe it is from RPG Maker.

The play and pause icons I got from http://www.iconarchive.com/show/play-stop-pause-icons-by-icons-land.html[^].

And finally, that robot that I am using in titles and also on the WPF sample I only know it is from a game, but I don't even know which one, as this is a very old animation that I had.

Version History

  • June 18, 2016: Added a download of a Windows Store version of the library;
  • October 16, 2013: Added a Shoot'em Up game sample, changed the license and added the codeplex link;
  • August 25, 2013: Added sounds to the Cyberminds57 animation and added the SegmentedTime class/animation fragment;
  • August 18, 2013: Added the PauseCondition, made the Cyberminds57 presentation pausable and finished some TODOs in the code, which includes how Dispose is expected to be used in this framework;
  • August, 8, 2013: First version.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
PraiseHelpful Pin
Sushmita Roy7-Jan-19 0:09
professionalSushmita Roy7-Jan-19 0:09 
GeneralVery helpful article. Pin
geniuscapri19-Oct-17 2:15
professionalgeniuscapri19-Oct-17 2:15 
GeneralRe: Very helpful article. Pin
Paulo Zemek19-Oct-17 11:46
mvaPaulo Zemek19-Oct-17 11:46 
GeneralMy vote of 5 Pin
Renju Vinod16-Sep-13 19:00
professionalRenju Vinod16-Sep-13 19:00 
GeneralRe: My vote of 5 Pin
Paulo Zemek17-Sep-13 6:17
mvaPaulo Zemek17-Sep-13 6:17 
GeneralMy vote of 5 Pin
netizenk12-Sep-13 9:16
professionalnetizenk12-Sep-13 9:16 
GeneralRe: My vote of 5 Pin
Paulo Zemek12-Sep-13 9:41
mvaPaulo Zemek12-Sep-13 9:41 
GeneralMy vote of 5 Pin
Danielvrhyn11-Sep-13 20:28
Danielvrhyn11-Sep-13 20:28 
GeneralRe: My vote of 5 Pin
Paulo Zemek12-Sep-13 3:45
mvaPaulo Zemek12-Sep-13 3:45 
Thanks.
GeneralMy vote of 5 Pin
Athari10-Sep-13 21:41
Athari10-Sep-13 21:41 
GeneralRe: My vote of 5 Pin
Paulo Zemek11-Sep-13 3:54
mvaPaulo Zemek11-Sep-13 3:54 
GeneralMy vote of 5 Pin
Uday P.Singh10-Sep-13 0:41
Uday P.Singh10-Sep-13 0:41 
GeneralRe: My vote of 5 Pin
Paulo Zemek10-Sep-13 2:12
mvaPaulo Zemek10-Sep-13 2:12 
GeneralMy vote of 5 Pin
Marcelo Ricardo de Oliveira5-Sep-13 12:38
mvaMarcelo Ricardo de Oliveira5-Sep-13 12:38 
GeneralRe: My vote of 5 Pin
Paulo Zemek5-Sep-13 14:38
mvaPaulo Zemek5-Sep-13 14:38 
GeneralMy vote of 5 Pin
Adam David Hill5-Sep-13 2:05
professionalAdam David Hill5-Sep-13 2:05 
GeneralRe: My vote of 5 Pin
Paulo Zemek5-Sep-13 4:00
mvaPaulo Zemek5-Sep-13 4:00 
QuestionMissed this one Pin
Sacha Barber20-Aug-13 23:37
Sacha Barber20-Aug-13 23:37 
AnswerRe: Missed this one Pin
Paulo Zemek21-Aug-13 4:30
mvaPaulo Zemek21-Aug-13 4:30 
GeneralMy vote of 5 Pin
gore0120-Aug-13 20:27
gore0120-Aug-13 20:27 
GeneralRe: My vote of 5 Pin
Paulo Zemek21-Aug-13 4:30
mvaPaulo Zemek21-Aug-13 4:30 
GeneralMy vote of 5 Pin
Gergo Bogdan15-Aug-13 5:12
Gergo Bogdan15-Aug-13 5:12 
GeneralRe: My vote of 5 Pin
Paulo Zemek15-Aug-13 5:18
mvaPaulo Zemek15-Aug-13 5:18 
QuestionMy Vote of 5 Pin
RaviRanjanKr14-Aug-13 5:27
professionalRaviRanjanKr14-Aug-13 5:27 
AnswerRe: My Vote of 5 Pin
Paulo Zemek14-Aug-13 5:28
mvaPaulo Zemek14-Aug-13 5:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.