Trident part 9 – key frames

June 25th, 2009

Over the course of this week i’m talking about different concepts in the Trident animation library for Java applications. Part nine talks about key frames that allow interpolating properties between more than two values.

Key frames

The sample application in multiple timelines example has the following basic timeline:

this.rolloverTimeline = new Timeline(this);
this.rolloverTimeline.addPropertyToInterpolate("backgroundColor",
		Color.yellow, Color.black);

This creates a timeline that animates the backgroundColor field from yellow to black when the timeline is played. What if you want to animate this field from yellow to green, and then to black? One option is to create two timelines, one to animate from yellow to green, and another to animate from green to black. Since the second timeline needs to wait until the first one ends, you will have to either use the timeline callbacks, or create a parallel timeline scenario.

However, there is a simpler solution for interpolating the value of the specific field between more than two values – key frames. A key frame defines the value of the field at the particular timeline duration fraction (not timeline position).

Key times and key values

In our example, the timeline to interpolate the background color will have three key frames:

  • The beginning key frame at key time 0.0f with associated key value of Color.yellow
  • The intermediate key frame at the matching key time (any value between 0.0f and 1.0f based on the application requirement) with associated key value of Color.green
  • The ending key frame at key time 1.0f with associated key value of Color.black

Each key frame has two mandatory associated properties: key time and key value. Key times must form a strictly increasing sequence that starts at 0.0f and ends at 1.0f. Key values must have either an explicit or an implicit property interpolator. This interpolator must be able to compute interpolated value for any successive pair of key values; this value should be of a class that can be passed to the public setter of the relevant property.

Simple example

To put it all together, here is the definition of a key frame-driven timeline for the example above:

this.rolloverTimeline = new Timeline(this);

KeyValues alphaValues = KeyValues.create(Color.yellow,
		Color.green, Color.black);
KeyTimes alphaTimes = new KeyTimes(0.0f, 0.5f, 1.0f);
this.rolloverTimeline.addPropertyToInterpolate("backgroundColor",
		new KeyFrames(alphaValues, alphaTimes));

Here, we specify that the backgroundColor starts at yellow, goes to green at half the duration of the timeline, and then goes to black at the end.

Simple key frame example

The SWT application discussed below implements a simple infinite progress indication illustrated in this screenshot:

The bluish highlighter moves from left to right, fading in as it appears on the left edge, and fading out as it disappears on the right edge. There are two properties that control the appearance of the highlighter:

  • xPosition – an integer property that is linearly interpolated between the left X and the right X.
  • alpha – a float property that starts at 0.0f, goes to 1.0f at 30% of the timeline duration, stays at 1.0f until the timeline reaches its 70% mark and then goes back to 0.0f

The alpha property in this example is interpolated using key frames.

The progress indication panel is an SWT Canvas with two fields and matching public setters:

public static class ProgressPanel extends Canvas {
	private int xPosition;

	private float alpha;

	public void setXPosition(int position) {
		xPosition = position;
	}

	public void setAlpha(float alpha) {
		this.alpha = alpha;
	}
}

The constructor of this panel wires a mouse listener that starts the indefinite progress animation. The boolean started field tracks whether this animation has been started:

private boolean started;

public ProgressPanel(Composite parent) {
	super(parent, SWT.DOUBLE_BUFFERED);

	this.xPosition = 0;
	this.alpha = 0;

	this.addMouseListener(new MouseAdapter() {
		@Override
		public void mouseUp(MouseEvent e) {
			if (started)
				return;
			start();
			started = true;
		}
	});
}

The start() method creates a timeline that interpolates the X position and alpha of the progress highlight. The X position is a simple interpolation between two values (taking into account that the highlight should not be painted outside the track). The alpha interpolation uses key frames:

public void start() {
	Timeline progressTimeline = new Timeline(this);

	int startX = (this.getBounds().width - INNER_WIDTH) / 2 + 18
			+ HIGHLIGHTER_WIDTH / 2;
	int endX = (this.getBounds().width + INNER_WIDTH) / 2 - 18
			- HIGHLIGHTER_WIDTH / 2;
	progressTimeline
			.addPropertyToInterpolate("xPosition", startX, endX);

	KeyValues alphaValues = KeyValues.create(0.0f, 1.0f, 1.0f, 0.0f);
	KeyTimes alphaTimes = new KeyTimes(0.0f, 0.3f, 0.7f, 1.0f);
	progressTimeline.addPropertyToInterpolate("alpha", new KeyFrames(
			alphaValues, alphaTimes));

	progressTimeline.setDuration(1500);
	progressTimeline.playLoop(RepeatBehavior.LOOP);
}

The panel constructor also creates a repaint timeline so that the progress animation is properly reflected on the screen:

new SWTRepaintTimeline(this).playLoop(RepeatBehavior.LOOP);

The actual painting is done in a custom PaintListener added in the panel constructor. The full code can be found in the test.swt.ProgressIndication class. It uses the matching SWT graphics operations to paint the overall background, the inner gradient background and contour, the track and the track highlight. The track highlight painting uses the current values of both xPosition and alpha fields to display the correct visuals.

Click below for the WebStart demo of the Swing version