Sorry about the red box, but we really need you to update your browser. Read this excellent article if you're wondering why we are no longer supporting this browser version. Go to Browse Happy for browser suggestions and how to update.

Animation

It's common to think of an animation as a series of images that change over time; however, to understand the framework of the Animation API for BlackBerry device applications, you need to think of animation in a more abstract way. Animation is the change over time of any property or set of properties according to specified rules. The changing property might be the location of an image that is displayed on the screen, in which case the animation is an object moving on the screen. But the properties might be color or opacity, in which case the animation might be a pulsating object that constantly changes color or one that cycles between being transparent and opaque.

A framework that supports this more abstract type of animation requires objects that include properties to animate. These objects are called Animatable objects. Objects that you want to animate have to expose one or more of their properties to be updated by the animation framework. They do this either by using one of the predefined Animatable objects or by creating their own classes that implement the Animatable interface.

The rendering of the animation is treated as a separate task that is not handled automatically by the animation engine. The animation engine updates the animatable property values periodically according to rules that you specify using the API. You must write your own rendering code and register a listener to be notified when an update occurs so that you can render the current state of the animation. The update can be images in updated locations, images with different colors, or something even more creative.

The animation engine must be supplied with rules that tell it how to change the animatable property values under its control. The animation framework uses the traditional animation concepts of key frames and easing curves. The key frames specify the values that the properties should have at various points during the animation. The easing curves specify the dynamics of how transitions are made from one key frame to the next. The animation framework uses an AnimationKeyframeSequence object to specify key frames and an Animation object lets you specify various easing curves.

The animation framework lets you group animations so that you can control them as a group. It also lets you set up triggers that cause an animation to start and stop.

Animation API

The primary classes in the Animation API are Animator and Animation. To use the primary classes, you will also work with the AnimationKeyframeSequence class and the AnimatedScalar class or AnimatedVector class. All of these classes are in the net.rim.device.api.animation package.

You create and control animations using the Animator class. You invoke one of its addAnimation() methods, which returns an Animation object. The target of your animation must implement the Animatable interface. In most cases, you can use either AnimatedScalar or AnimatedVector to represent the animatable properties of the object that you want to animate. If you can't use these classes to represent the properties, there are also classes in the net.rim.device.api.math package that implement Animatable. If none of the predefined classes work for your application, you can build a custom animatable object by creating a class that implements Animatable.


Class diagram of the Animation API

Class or interface

Description

Animator

This class represents the root node of the set of animations that you want to manage. You can invoke one of its addAnimation() methods to create an animation. You can invoke begin() and end() to start and stop the animation that you create.

Animation

This class represents an animation and lets you manage the animation.

Animatable

This interface describes the set of methods that you have to implement to create your own Animatable object.

AnimatedScalar

This class implements a predefined, animatable scalar property.

AnimatedVector

This class implements a predefined, animatable vector property.

AnimationKeyframeSequence

This class represents a sequence of key frames and lets you specify reference points in an animation sequence. The animation framework interpolates between the reference points that you specify.

AnimationGroup

This class represents a group of animations and lets you control these animations as a group instead of individually.

AnimationTrigger

This class represents an object that you can add to an animation and later use to trigger the animation to start or stop.

AnimationValue

This class provides a wrapper for the value of an animatable property that is being animated.

AbstractAnimation

This class defines the basic functionality for animations and animation groups.

Code sample: Creating and starting a basic animation

The following code sample doesn't create a complete animation. It illustrates the key steps that are required to set up a simple animation of a vector animatable object.

To enhance the code sample to make it a working animation, you would have to do the following:
  • Add code to draw an object on the screen at the location that is specified by the position variable.
  • Set up a listener for the animation engine to invoke each time it updates the position of the object.
  • In response to each call, draw your object on the screen at the current position.
 
Animator animator = new Animator(25);
AnimatedVector position = new AnimatedVector( new float[] { 0.0f, 0.0f }, 0 ); 
Animation animation = animator.addAnimationTo(
 position,
 AnimatedVector.ANIMATION_PROPERTY_VECTOR, 
 new float[] { 100.0f, 100.0f }, 
 0,
 Animation.EASINGCURVE_LINEAR, 1000L);

animation.setRepeatCount(1.0f);
animation.begin(1000L);

Key frames

Animations are created by presenting a series of images quickly enough that the human eye doesn't sense an abrupt transition between the images. If the differences between adjacent images in the series are kept small enough, someone viewing the animation sees it as a smooth, changing scene instead of a series of images.

Each static image in an animation is called a frame. The term frame originated from film technology. A strip of film is partitioned into a series of images that are separated from each other by thin, unused portions of film called frame lines. Together the unused portions of the film and the edges of the film appear like a picture frame around each individual image.

One method of creating an animation is to create each of the frames that you want to present individually. This method is called keyframing. Keyframing gives the animator precise control over the animation, but it's very labor-intensive. Another method is to create only some of the most important frames. The frames that you choose to create are called key frames and are pivotal or complex members of the series of images.

In classical animation, a senior animator might create the key frames and junior animators would fill in the in-between frames. In computer animation, the animator can specify the key frames and the computer can use interpolating algorithms to generate the in-between frames. The interpolation process is called tweening.

With the Animation API, you specify the key frames using the KeyframeSequence class and the animation framework uses interpolation to calculate the values of the animatable properties of the animation between the key frames.

Easing curves

Easing curves are functions that specify the speed of an animation. You can use them to make animations look more realistic without requiring you to do physics calculations. For example, when you animate a dropping ball, you want the ball to speed up as it drops. An easing curve that eases in creates that effect.

The domain of an easing function is the completed fraction of the total duration of the animation. The range is the completed fraction of the total animating that is required. If e(t) is an easing function and the position of an animatable object changes from p1 to p2 over a time period of d milliseconds, then at an intermediate time t, the object is at position p = p2 - p1*e(t) + p1.

Easing curve plots help you visualize the effect of an easing curve on an animation. The following plot uses a normalized domain of [0,1] to represent the entire duration of the animation and displays an EASINGCURVE_BOUNCE_IN easing curve.

Graph depicting an easing curve.

There are several terms that are used to classify easing curves.

Term Definition
Ease in The animation accelerates.
Ease out The animation decelerates.
Ease in-out The first half of the animation accelerates and the second half decelerates.
Ease out-in The first half of the animation decelerates and the second half accelerates.

The Animation API supports nearly 50 easing curves. If none of these curves work for your application, you can specify a custom easing curve using cubic B├ęzier splines.

Predefined classes that implement the Animatable interface

Animatable class

Description

net.rim.device.api.animation

AnimatedScalar

Defines a scalar value.

AnimatedVector

Defines a vector value.

net.rim.device.api.math

Vector2f

Defines a 2-element vector of floating point numbers.

Vector3f

Defines a 3-element vector of floating point numbers.

Vector4f

Defines a 4-element vector of floating point numbers.

Color3f

Defines a color made up of red, green, and blue components.

Color4f

Defines a color made up of red, green, blue, and alpha components.

Quaternion4f

Defines a 4-element quaternion that represents the orientation of an object in space.

Transform2D

Defines a 2-D transformation.

Transform3D

Defines a 3-D transformation.

Create a custom class that implements the Animatable interface

import net.rim.device.api.animation.*;
 
//Create a class that implements the Animatable interface.  
 public class MyAnimatable implements Animatable
 {

//Create integer constants to represent each of the properties of 
//the class and create an integer array to store the number of components 
//in each property. Declare variables to hold the data for 
//all of the components of the class.  
      public static final int ANIMATION_PROPERTY_XY = 0;
      public static final int ANIMATION_PROPERTY_WIDTH = 1;
      public static final int ANIMATION_PROPERTY_HEIGHT = 2;
      public static final int ANIMATION_PROPERTY_COUNT = 3;
      private static final int[] ANIMATION_PROPERTY_COMPONENT_COUNTS = { 2, 1, 1 };
      
      public float x, y, width, height;

//Create a constructor that accepts and sets values of all of 
//the components of the animatable properties.        
      public MyAnimatable(float x, float y, float height, float width) 
      {
          this.x = x;
          this.y = y;
          this.width = width;
          this.height = height;
      }

//Implement getAnimationValue(). The AnimationValue class is used by the 
//animation framework to encapsulate the values of animatable objects it is 
//animating. Invoke setFloat() to store the component values of this 
//particular animatable object in value.        
      public void getAnimationValue(int property, AnimationValue value)
      {
          switch(property) 
          {
              case ANIMATION_PROPERTY_XY:
                  value.setFloat(0, this.x);
                  value.setFloat(1, this.y);
                  break;
             case ANIMATION_PROPERTY_WIDTH:
                  value.setFloat(0, this.width);
                  break;
              case ANIMATION_PROPERTY_HEIGHT:
                  value.setFloat(0, this.height);
                  break;
          }
      }
      
      public void setAnimationValue(int property, AnimationValue value)
      {

//Implement setAnimationValue(). Invoke getFloat() to retrieve the updated 
//component values for this animatable object from value.
          switch(property) 
          {
              case ANIMATION_PROPERTY_XY:
                  this.x = value.getFloat(0);
                  this.y = value.getFloat(1);
                  break;
              case ANIMATION_PROPERTY_WIDTH:
                  this.width = value.getFloat(0);
                  break;
              case ANIMATION_PROPERTY_HEIGHT:
                  this.height = value.getFloat(0);
                  break;
          }
      }

//Implement getAnimationPropertyComponentCount(). A check is performed to make sure that 
//the integer property parameter is valid, and then the count that is stored 
//in the ANIMATION_PROPERTY_COMPONENT_COUNTS array is returned.
      public int getAnimationPropertyComponentCount(int property)
      {
          if (property >= 0 && property < ANIMATION_PROPERTY_COUNT)
              return ANIMATION_PROPERTY_COMPONENT_COUNTS[property];
          return 0;
      }
 }

Code sample: Animating a dropping ball in one dimension

import net.rim.device.api.animation.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.animation.*;


public class SimpleAnimation extends UiApplication
{  
    public static void main( String[] args )
    {
        SimpleAnimation app = new SimpleAnimation();
        app.enterEventDispatcher();
    }
    
    public SimpleAnimation()
    {
        pushScreen(new AnimationScreen());
    }
    
}

class AnimationScreen extends MainScreen implements AnimatorListener
{
    private RectangleToMove _rect;
    private Animator  _animator;
    private Animation _xanimation;
    private Animation _yanimation;
    private boolean _bAnimating;
    public static final int BALL_WIDTH = 50;
    
    public AnimationScreen()
    {
        _bAnimating = false;
        int midScreen = Display.getWidth()/2;
        int endScreen = Display.getHeight();
        _rect = new RectangleToMove(midScreen,BALL_WIDTH);
        _animator = new Animator(30);
        _animator.setAnimatorListener(this);
        _yanimation = _animator.addAnimationFromTo(_rect.getY(),
          AnimatedScalar.ANIMATION_PROPERTY_SCALAR, BALL_WIDTH, 
          endScreen-BALL_WIDTH, Animation.EASINGCURVE_BOUNCE_OUT, 6000L);
        _yanimation.setRepeatCount(5f);
        _yanimation.begin(0);
    }
    
    protected void paint(Graphics g)
    {
        if(_bAnimating)
        {
           _rect.draw(g);
        }
    }
    
    public void animatorUpdate()
    {
        invalidate();
        doPaint();
    }
    
    public void animatorProcessing(boolean processing)
    {
        _bAnimating = processing;
    } 
}

class RectangleToMove
{
    private int xPos;
    private AnimatedScalar yPos;
    
    public void draw(Graphics g)
    {
        g.setBackgroundColor(Color.BLACK);
        g.clear();
        g.setColor(Color.SLATEGRAY);
        g.fillEllipse(xPos,yPos.getInt(),
          xPos+AnimationScreen.BALL_WIDTH,yPos.getInt(),xPos,
          yPos.getInt()+AnimationScreen.BALL_WIDTH,0,360);
    }    
    
    public int getX()
    {
        return xPos;
    }
    
    public AnimatedScalar getY()
    {
        return yPos;
    }
    
    RectangleToMove(int x,int y)
    {
        xPos = x;
        yPos = new AnimatedScalar(y);
    }
}