Over the course of the next few days i’m going to talk about different concepts in the Trident animation library for Java applications. Part three talks about APIs to play, loop, resume, suspend and cancel timelines, as well as registering callbacks to listen to timeline life cycle events.

Timeline states

A timeline goes through different timeline states. The Timeline.TimelineState enum lists all possible timeline states, with the basic ones being:

  • Idle for timelines that are not playing. A timeline is idle when it’s been created but not played, or after it has finished playing
  • Playing forward for timelines that interpolate fields from start value to end value.
  • Playing reverse for timelines that interpolate fields from end value to start value.
  • Done for timelines that have finished playing. A done timeline becomes idle after notifying all listeners (see below).

Playing timelines

When the timeline is created, it is in the idle state. An idle timeline can be configured by the application code – even after it has finished playing. The configuration includes adding properties to interpolate, changing the initial delay and duration, adding callbacks and changing the ease function.

To start playing a timeline use the Timeline.play() method. At every pulse the timeline will interpolate all registered properties (using the public setters), as well as notify all registered callbacks. If the timeline is already playing, it will continue playing from the same point.

Some scenarios required playing the timeline in reverse. In the example below we want to animate the button foreground from blue to red when the mouse moves over the button – this is done in the mouseEntered method. To provide consistent UI behavior, we also want to animate the foreground color from red back to blue when the mouse is moved away from the button – this is done by calling the Timeline.playReverse() in the mouseExisted method.

final Timeline rolloverTimeline = new Timeline(button);
rolloverTimeline.addPropertyToInterpolate("foreground", Color.blue,
		Color.red);
button.addMouseListener(new MouseAdapter() {
	@Override
	public void mouseEntered(MouseEvent e) {
		rolloverTimeline.play();
	}

	@Override
	public void mouseExited(MouseEvent e) {
		rolloverTimeline.playReverse();
	}
});

Suppose the user moves the mouse over the button and then quickly moves it away. You do not want to have the reverse play start from the end value (full red color) – this will create a noticeable color flicker on the screen. Internally, playReverse() detects that this timeline is already playing (forward), and starts playing it in reverse from its current position.

Replaying timelines

While play() and playReverse() respect the current timeline position for already playing timelines, some scenarios require restarting the timeline. The Timeline.replay() and Timeline.replayReverse() can be used in these cases. Code example below shows the replay() API used to restart the rollover animation that interpolates the background color of a single grid rectangle from yellow to black color:

public SnakePanelRectangle() {
	this.backgroundColor = Color.black;
	this.isRollover = false;

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

public void setRollover(boolean isRollover) {
	if (this.isRollover == isRollover)
		return;
	this.isRollover = isRollover;
	if (this.isRollover) {
		this.rolloverTimeline.replay();
	}
}

Looping timelines

While most timelines need to play only once, some application scenarios require running timelines in loop. Pulsating the system tray icon to indicate new messages or showing an indefinite progress while your application connects over a slow line are examples of looping timelines.

Looping timelines are created and configured in exactly the same way as regular timelines, and they can interpolate float and custom properties of the associated main timeline object. The only difference is the way the looping timeline is played. There are two Timeline methods to start playing a looping timeline:

  • Timeline.playLoop(RepeatBehavior)
  • Timeline.playLoop(int, RepeatBehavior)

The first method starts an infinite loop (at least until the timeline is canceled). The second method runs the timeline for the specified number of loops. The Timeline.RepeatBehavior enum specifies what happens when the looping timeline reaches the “end” of the loop. Each timeline loop changes the internal duration fraction which is a number between 0.0 and 1.0. While a regular timeline ends once the fraction reaches the value 1.0, a looping timeline continues. The difference between the repeat behaviors is in the way the timeline fraction is computed:

  • In the loop mode the timeline fraction starts from 0.0, is interpolated to 1.0, and once that value is reached, it is reset it back to 0.0.
  • In the reverse mode, the timeline fraction is interpolated during odd loops from 0.0 to 1.0, and is interpolated during even loops from 1.0 down to 0.0.

As an example, the loop mode can be used for circular indefinite progress indication, where the matching “lead” angle is interpolated between 0 and 360 degrees. The reverse mode can be used for displaying indefinite linear progress indication that oscillates between the left and right markers.

Additional timeline operations

A timeline can be put in the suspended state by calling the Timeline.suspend() method. A suspended timeline can be resumed with the Timeline.resume() method.

To cancel a playing timeline, call the Timeline.cancel() method. In addition, there is a method to indicate that a looping timeline should stop once it reaches the end of the loop. For example, suppose that you have a pulsating animation of system tray icon to indicate unread messages. Once the message is read, this animation is canceled in the application code. However, immediate cancellation of the pulsating animation may result in jarring visuals, especially if it is done at the “peak” of the pulsation cycle. Calling Timeline.cancelAtCycleBreak() method will indicate that the looping animation should stop once it reaches the end of the loop.

Tracking timeline state

Simple application scenarios create timelines, configure them with fields to interpolate and then play them. However, a more complicated application logic may require tracking the state changes of the timeline. The Timeline.addCallback(TimelineCallback) allows registering a custom callback that will be notified in the following cases:

  • TimelineCallback.onTimelineStateChanged() – this is called whenever the timeline state is changed. For example, calling Timeline.suspend() will notify all the registered listeners that the timeline has changed its state from playing to suspended.
  • TimelineCallback.onTimelinePulse() – this is called on every timeline pulse.

The second method can be used by applications that do not wish to add public setters for all the fields that participate in the timelines. Instead of using the Timeline.addPropertyToInterpolate() to interpolate the fields via public setters, a timeline callback that interpolates the fields directly in the onTimelinePulse() can be used.

Over the course of the next few days i’m going to talk about different concepts in the Trident animation library for Java applications. Part two talks about APIs to interpolate primitive and non-primitive fields of Java objects.

Interpolation of primitive fields

A timeline allows changing field values of the associated object. For example, in a fade-in animation the timeline will interpolate the value of alpha field from 0.0 to 1.0. There are two steps involved in setting up such timeline.

The first step is to create a Timeline instance passing the main object. The timeline is then configured to interpolate one or more fields of this main object. Let’s see a simple example:

import org.pushingpixels.trident.Timeline;

public class HelloWorld {
	private float value;

	public void setValue(float newValue) {
		System.out.println(this.value + " -- " + newValue);
		this.value = newValue;
	}

	public static void main(String[] args) {
		HelloWorld hello = new HelloWorld();
		Timeline timeline = new Timeline(hello);
		timeline.addPropertyToInterpolate("value", 0.0f, 1.0f);
		timeline.play();

		try {
			Thread.sleep(3000);
		} catch (Exception exc) {
		}
	}
}

Here, the timeline has the associated HelloWorld instance; this timeline is instructed to interpolate the value field of that instance from 0.0 to 1.0 over the duration of the timeline.

There is an important assumption that the application code must honor. Each field added with the addPropertyToInterpolate must have the matching public setter.

A timeline can interpolate multiple fields. In the following example the timeline will change values of three fields at each timeline pulse:

Timeline timeline = new Timeline(circle);
timeline.addPropertyToInterpolate("x", initX, finalX);
timeline.addPropertyToInterpolate("y", initY, finalY);
timeline.addPropertyToInterpolate("opacity", 1.0f, 0.0f);

Interpolating non-primitive fields

In addition to interpolating primitive fields, the Timeline.addPropertyToInterpolate APIs allow the application code to interpolate fields of other types. Each interpolation is using the matching property interpolator that implements the org.pushingpixels.trident.interpolator.PropertyInterpolator interface:

public interface PropertyInterpolator {
	public Class getBasePropertyClass();

	public T interpolate(T from, T to, float timelinePosition);
}

The interpolate method is used at each timeline pulse to compute the interpolated value. To interpolate a field using the specific property interpolator call the following Timeline API:

addPropertyToInterpolate(String, Object, Object, PropertyInterpolator)

The parameters are:

  • The field name.
  • The start value.
  • The end value.
  • The property interpolator.

Note that the application is responsible to make sure that the object passed to the timeline constructor has a public setter accepting the interpolated value returned by the interpolate implementation of this property interpolator.

The PropertyInterpolator.getBasePropertyClass is not used when the application code explicitly passes the property interpolator – and it is safe to return any value (including null) from it. This method is used only during dynamic lookup of custom property interpolators.

Additional interpolation capabilities

The addPropertyToInterpolate API above is used to interpolate a specific field from the given start value to the given end value. Application code can also use the following variations of this API:

  • addPropertyToInterpolate(Object, String, Object, Object) – to interpolate the specified field of an object different from the main timeline object. This object is passed as the first parameter to this method
  • addPropertyToInterpolate(Object, String, Object, Object, PropertyInterpolator) – to interpolate the specified field of an object different from the main timeline object using the specified property interpolator

When the start value is not known at timeline initialization time, the following APIs can be used to interpolate the field from its current value (as known at runtime) to the specified end value:

  • addPropertyToInterpolateTo(String, Object) – to interpolate the specified field of the main timeline object
  • addPropertyToInterpolateTo(Object, String, Object) – to interpolate the specified field of an object different from the main timeline object. This object is passed as the first parameter to this method
  • addPropertyToInterpolateTo(Object, String, Object, PropertyInterpolator) – to interpolate the specified field of an object different from the main timeline object using the specified property interpolator

Over the course of the next few days i’m going to talk about different concepts in the Trident animation library for Java applications. Part one introduces the “Hello world” example.

Timeline is the most important concept in Trident. Here is a simple example that illustrates the basic timeline terms:

import org.pushingpixels.trident.Timeline;

public class HelloWorld {
	private float value;

	public void setValue(float newValue) {
		System.out.println(this.value + " -- " + newValue);
		this.value = newValue;
	}

	public static void main(String[] args) {
		HelloWorld hello = new HelloWorld();
		Timeline timeline = new Timeline(hello);
		timeline.addPropertyToInterpolate("value", 0.0f, 1.0f);
		timeline.play();

		try {
			Thread.sleep(3000);
		} catch (Exception exc) {
		}
	}
}
  • We import the org.pushingpixels.trident.Timeline class.
  • We define a private float attribute of this class, and provide a public setter for this attribute.
  • We create a new instance of our test class and a timeline associated with that instance.
  • We specify that the value attribute of that instance should be interpolated from 0.of to 1.0f when the timeline is played.
  • Finally, we play the timeline.
  • The Thread.sleep(3000) makes sure that the application waits long enough for the timeline to finish playing

Here is the output of this application:

0.0 -- 0.0
0.0 -- 0.436
0.436 -- 0.514
0.514 -- 0.594
0.594 -- 0.672
0.672 -- 0.75
0.75 -- 0.828
0.828 -- 0.906
0.906 -- 0.984
0.984 -- 1.0
1.0 -- 1.0

Note that the actual numbers depend on the current system load. The setter is called by the timeline at timeline pulses. A timeline pulse is the point where the timeline “wakes up” and changes the values of all registered fields. The values are changed based on how much time has passed since the timeline has started playing.

The three basic timeline concepts illustrated in this sample are:

  • A timeline is associated with an object.
  • A timeline interpolates values of object fields using the public setters of the relevant class.
  • The field values are changed at timeline pulses.

One of the best presentations I’ve heard at this year’s EclipseCon was given by Alexandra Imrie on the topic of usability. Usability is sometimes abandoned altogether due to budget constraints, often addressed as a hasty afterthought, and almost never viewed by developers as a necessity. In this interview Alex looks at the usability angle from the end user perspective, highlighting its importance to us, the developers.
alexandra_imrie_thumbTell us a little bit about yourself

I’m Alex Imrie, and I work at BREDEX GmbH in Braunschweig, Germany. I have various roles at the company including Marketing, customer demonstrations, training and support as well as documentation and conceptual design for some of our software. I also do some testing. The unifying part of all these roles is that I represent the user and customer side – I have a non-technical background in linguistics, but a good knowledge of how to work with our software from the user’s perspective, so that makes me a good advocate for our users and customers!

What does usability mean to you?

The main aspect of usability for me is that software should help the user to do his job, not make the job harder! The application as a whole should be consistent in its work-flows and allow the user to work in different ways (and each way should be as simple as possible). The software should adapt to the user, not vice-versa. I also believe that most applications shouldn’t involve the user in internal technical aspects. The user should be able to work with the software without knowing how it ticks in the background.

Even if a piece of software is well written in these terms, we need ways of helping new users with the software. From context-sensitive help to tutorials and examples, it should be easy to find out more about the application without having to read the whole manual or attend weeks of training. Obviously, training may be necessary for some applications, or to learn how to work best with an application, but it shouldn’t be necessary from the very outset.

Why should developers be aware of usability aspects of the products they’re creating?

I think the very simple answer is because the products are meant to be used by the users! Whether the users themselves are developers or not, we always need to bear the target group in mind when an application is being designed, or when new areas are being added. Features are all very well and good, but if the application is hard to understand or hard to use, then users will be alienated and annoyed. In terms of commercial software, this can lead to dissatisfied users – and word of mouth about bad software can spread quickly.

Can usability be sacrificed for internal products that people will have to use in any case?

On the contrary, I think that internal products can be a good way to practice making software more usable. After all, internal feedback can be quickly incorporated into the product and developers can really get the chance to see how users think. Internal users are still users at the end of the day, and deserve to have an application that will make their life easier, not more frustrating! Another way to think of this is in terms of the support costs your team will save by not having to answer “basic” questions about how to use the software. In smaller companies especially, making usable software can be conducive to good relations between developers and users. Making usability a priority internally brings value for external products. The design process will no longer separate functionality from usability – all functions will be designed to be usable right from the start. The benefits are endless, really.

Why is usability at best an afterthought, and in most cases non-existent in business applications?

On the one hand, it’s often very difficult to see things from the user’s perspective. Developers think like developers, and rightly so. It’s just very difficult to put yourself into someone else’s shoes and forget all you know about the internal workings of something. (This is the same reason that developers aren’t necessarily good as functional testers!).

The other issue is time and costs. If usability isn’t explicitly considered at the beginning, it can be harder to add later when the need arises. With deadlines for features and releases, it can be hard to make the decision to lose a feature to gain usability. I think that too much time is often spent working out how a feature should work technically, and not how the user should be able to access it, even though the two aspects actually belong together.

What are the most common usability errors that you see in popular applications?

One thing I notice quite frequently is inconsistent behaviour. Like some delete actions requiring confirmation and others not. Or options being available in a context menu in one area, but not in another. This also affects menus where certain entries are completely missing instead of just disabled when they are not available. That can be confusing!

These are also quite minimal things that can easily be changed. I like knowing where I am in a workflow – what are the prerequisites, and what must I do next. I appreciate progress dialogs for so I can see how much longer I need to wait, and which reassure me that the application hasn’t crashed. I also find it difficult to work with applications that only offer one way of doing things. I’m a fan of using keyboard shortcuts, so a mouse-only application can be frustrating.

Do we need to facilitate an emotional connection between the users and the ap-plications?

I think so. I think that a user should feel “led” by an application, and should feel safe in the knowledge that the application will not let him or her do anything horribly wrong. One concept we use quite extensively at Bredex is the idea of the “first 15 minutes”. I think this can be where a large part of the emotional connection comes from. At the very least, these first fifteen minutes should be the easiest, and there should be no way at all of running into trouble. Layout and graphics can play a large part in making an application simply look more friendly, and I’m particularly fond of welcome screens, like those in the Eclipse framework, to present important information in an accessible way at the start up of an application.

Do we still need manuals and organized help in the age of wikis, blogs, forums and mailing lists?

Yes, I believe we do. The idea of the documentation as a wiki is certainly appealing, but I think there is still a place for manuals. Wikis are certainly more dynamic, but this can be a problem – with manuals, you can always tell which version of a software the manual is for. For me, the appeal of a manual is having one single source of reference for all of the possibilities of a piece of software. As well as describing tasks, a manual can also introduce best practices and can be directly integrated into the software as context sensitive help to save trawling through a forum or waiting for an answer in a mailing list. Media like mailing lists and forums certainly have their place, but I see them more as a place to ask very specific questions, whereas the manual should deal with the various tasks you can do with the program and the concepts of the software.

No documentation, and the users will be upset. A lot of documentation and nobody will read it. How to find the “sweet spot”?

If I knew the answer to this question I’d be rich! Seriously, I think that a task-oriented approach to the documentation is the best way to go. For each task, pre- and post-requisites as well as related tasks should be linked. This makes searching for the piece of the documentation considerably easier. I also think it’s important to reduce the amount of explaining the documentation has to do. If the technical writer is spending days trying to get a section right, it’s a sure sign that the application itself is too compli-cated. If each and every text field has to be explained, that’s a hint that the labels for the text fields aren’t very good. Documentation is necessary, but it shouldn’t become an issue of “XYZ doesn’t work like ABC, so we’ll document it instead of fixing it”.

Is there anything else you’d like to add?

One thing we have found works well for us in making sure that usability is considered from the outset is to have the technical writer(s) (and also the testers) present at the design and concept meetings. These are usually the people who can tell you “that’s not how it works in other areas” or “that’s not clear enough” or even “why not do it another way”. By not having the same background and perspective as the developers, they can offer valuable advice for the usability of an application.