Custom Animations in Xamarin.Forms - Xamarin (original) (raw)

The Animation class is the building block of all Xamarin.Forms animations, with the extension methods in the ViewExtensions class creating one or more Animation objects. This article demonstrates how to use the Animation class to create and cancel animations, synchronize multiple animations, and create custom animations that animate properties that aren't animated by the existing animation methods.

A number of parameters must be specified when creating an Animation object, including start and end values of the property being animated, and a callback that changes the value of the property. An Animation object can also maintain a collection of child animations that can be run and synchronized. For more information, see Child Animations.

Running an animation created with the Animation class, which may or may not include child animations, is achieved by calling the Commit method. This method specifies the duration of the animation, and amongst other items, a callback that controls whether to repeat the animation.

In addition, the Animation class has an IsEnabled property that can be examined to determine if animations have been disabled by the operating system, such as when power saving mode is activated.

Create an animation

When creating an Animation object, typically, a minimum of three parameters are required, as demonstrated in the following code example:

var animation = new Animation (v => image.Scale = v, 1, 2);

This code defines an animation of the Scale property of an Image instance from a value of 1 to a value of 2. The animated value, which is derived by Xamarin.Forms, is passed to the callback specified as the first argument, where it's used to change the value of the Scale property.

The animation is started with a call to the Commit method, as demonstrated in the following code example:

animation.Commit (this, "SimpleAnimation", 16, 2000, Easing.Linear, (v, c) => image.Scale = 1, () => true);

Note that the Commit method does not return a Task object. Instead, notifications are provided through callback methods.

The following arguments are specified in the Commit method:

The overall effect is to create an animation that increases the Scale property of an Image from 1 to 2, over 2 seconds (2000 milliseconds), using the Linear easing function. Each time the animation completes, its Scale property is reset to 1 and the animation repeats.

Note

Concurrent animations, that run independently of each other can be constructed by creating an Animation object for each animation, and then calling the Commit method on each animation.

Child animations

The Animation class also supports child animations, which involves creating an Animation object to which other Animation objects are added. This enables a series of animations to be run and synchronized. The following code example demonstrates creating and running child animations:

var parentAnimation = new Animation ();
var scaleUpAnimation = new Animation (v => image.Scale = v, 1, 2, Easing.SpringIn);
var rotateAnimation = new Animation (v => image.Rotation = v, 0, 360);
var scaleDownAnimation = new Animation (v => image.Scale = v, 2, 1, Easing.SpringOut);

parentAnimation.Add (0, 0.5, scaleUpAnimation);
parentAnimation.Add (0, 1, rotateAnimation);
parentAnimation.Add (0.5, 1, scaleDownAnimation);

parentAnimation.Commit (this, "ChildAnimations", 16, 4000, null, (v, c) => SetIsEnabledButtonState (true, false));

Alternatively, the code example can be written more concisely, as demonstrated in the following code example:

new Animation {
    { 0, 0.5, new Animation (v => image.Scale = v, 1, 2) },
    { 0, 1, new Animation (v => image.Rotation = v, 0, 360) },
    { 0.5, 1, new Animation (v => image.Scale = v, 2, 1) }
    }.Commit (this, "ChildAnimations", 16, 4000, null, (v, c) => SetIsEnabledButtonState (true, false));

In both code examples, a parent Animation object is created, to which additional Animation objects are then added. The first two arguments to the Add method specify when to begin and finish the child animation. The argument values must be between 0 and 1, and represent the relative period within the parent animation that the specified child animation will be active. Therefore, in this example the scaleUpAnimation will be active for the first half of the animation, the scaleDownAnimation will be active for the second half of the animation, and the rotateAnimation will be active for the entire duration.

The overall effect is that the animation occurs over 4 seconds (4000 milliseconds). The scaleUpAnimation animates the Scale property from 1 to 2, over 2 seconds. The scaleDownAnimation then animates the Scale property from 2 to 1, over 2 seconds. While both scale animations are occurring, the rotateAnimation animates the Rotation property from 0 to 360, over 4 seconds. Note that the scaling animations also use easing functions. The SpringIn easing function causes the Image to initially shrink before getting larger, and the SpringOut easing function causes the Image to become smaller than its actual size towards the end of the complete animation.

There are a number of differences between an Animation object that uses child animations, and one that doesn't:

The Animation class also includes WithConcurrent methods that can be used to add child animations to a parent Animation object. However, their begin and finish argument values aren't restricted to 0 to 1, but only that part of the child animation that corresponds to a range of 0 to 1 will be active. For example, if a WithConcurrent method call defines a child animation that targets a Scale property from 1 to 6, but with begin and finish values of -2 and 3, the begin value of -2 corresponds to a Scale value of 1, and the finish value of 3 corresponds to a Scale value of 6. Because values outside the range of 0 and 1 play no part in an animation, the Scale property will only be animated from 3 to 6.

Cancel an animation

An application can cancel an animation with a call to the AbortAnimation extension method, as demonstrated in the following code example:

this.AbortAnimation ("SimpleAnimation");

Note that animations are uniquely identified by a combination of the animation owner, and the animation name. Therefore, the owner and name specified when running the animation must be specified to cancel the animation. Therefore, the code example will immediately cancel the animation named SimpleAnimation that's owned by the page.

Create a custom animation

The examples shown here so far have demonstrated animations that could equally be achieved with the methods in the ViewExtensions class. However, the advantage of the Animation class is that it has access to the callback method, which is executed when the animated value changes. This allows the callback to implement any desired animation. For example, the following code example animates the BackgroundColor property of a page by setting it to Color values created by the Color.FromHsla method, with hue values ranging from 0 to 1:

new Animation (callback: v => BackgroundColor = Color.FromHsla (v, 1, 0.5),
  start: 0,
  end: 1).Commit (this, "Animation", 16, 4000, Easing.Linear, (v, c) => BackgroundColor = Color.Default);

The resulting animation provides the appearance of advancing the page background through the colors of the rainbow.

For more examples of creating complex animations, including a Bezier curve animation, see Chapter 22 of Creating Mobile Apps with Xamarin.Forms.

Create a custom animation extension method

The extension methods in the ViewExtensions class animate a property from its current value to a specified value. This makes it difficult to create, for example, a ColorTo animation method that can be used to animate a color from one value to another, because:

The solution to this problem is to not have the ColorTo method target a particular Color property. Instead, it can be written with a callback method that passes the interpolated Color value back to the caller. In addition, the method will take start and end Color arguments.

The ColorTo method can be implemented as an extension method that uses the Animate method in the AnimationExtensions class to provide its functionality. This is because the Animate method can be used to target properties that aren't of type double, as demonstrated in the following code example:

public static class ViewExtensions
{
  public static Task<bool> ColorTo(this VisualElement self, Color fromColor, Color toColor, Action<Color> callback, uint length = 250, Easing easing = null)
  {
    Func<double, Color> transform = (t) =>
      Color.FromRgba(fromColor.R + t * (toColor.R - fromColor.R),
                     fromColor.G + t * (toColor.G - fromColor.G),
                     fromColor.B + t * (toColor.B - fromColor.B),
                     fromColor.A + t * (toColor.A - fromColor.A));
    return ColorAnimation(self, "ColorTo", transform, callback, length, easing);
  }

  public static void CancelAnimation(this VisualElement self)
  {
    self.AbortAnimation("ColorTo");
  }

  static Task<bool> ColorAnimation(VisualElement element, string name, Func<double, Color> transform, Action<Color> callback, uint length, Easing easing)
  {
    easing = easing ?? Easing.Linear;
    var taskCompletionSource = new TaskCompletionSource<bool>();

    element.Animate<Color>(name, transform, callback, 16, length, easing, (v, c) => taskCompletionSource.SetResult(c));
    return taskCompletionSource.Task;
  }
}

The Animate method requires a transform argument, which is a callback method. The input to this callback is always a double ranging from 0 to 1. Therefore, the ColorTo method defines its own transform Func that accepts a double ranging from 0 to 1, and that returns a Color value corresponding to that value. The Color value is calculated by interpolating the R, G, B, and A values of the two supplied Color arguments. The Color value is then passed to the callback method for application to a particular property.

This approach allows the ColorTo method to animate any Color property, as demonstrated in the following code example:

await Task.WhenAll(
  label.ColorTo(Color.Red, Color.Blue, c => label.TextColor = c, 5000),
  label.ColorTo(Color.Blue, Color.Red, c => label.BackgroundColor = c, 5000));
await this.ColorTo(Color.FromRgb(0, 0, 0), Color.FromRgb(255, 255, 255), c => BackgroundColor = c, 5000);
await boxView.ColorTo(Color.Blue, Color.Red, c => boxView.Color = c, 4000);

In this code example, the ColorTo method animates the TextColor and BackgroundColor properties of a Label, the BackgroundColor property of a page, and the Color property of a BoxView.