JavaFX’s animation timer provides access to synchronised timestamps, and every animation gets the same stamp. That’s a huge plus, but it means that JavaFX lacks native support for pausing. Asking an animation timer to stop doesn’t stop the master timer, it just unregisters your animation timer from the timing pulses.

Pausing an animation timer can be achieved by extending the AnimationTimer class to record the timestamps or length of a pause event. On restarting the animation, the total pause length is subtracted from each timestamp to give the appearance of a continuous animation.

In this project, we’ll create a mini animation player, with the ability to pause, stop and restart the animation timer.

The JavaFX Animation timer modified to allow pausing and restarting
20

How the Timer Works

Simply put, the start() and stop() methods of the animation timer aren’t very well named, because they don’t do what you might expect. We’ll start by discussing what they do accomplish, and then discuss how to modify our timer to add the functionality we need.

If you want to jump right into modifying timer, you can skip ahead here.

Master Timer

If you’re interested in how the animation timer sets itself up to receive updates from the JavaFX master timer, we’ve discussed it here. But, in short, the animation timer is not a timer in-and-of-itself. More accurately, it’s a Receiver. Every frame, JavaFX sends an animation pulse through the system, marked with the same timestamp.

Other classes – Timeline and Transition – extend the JavaFX Animation class. This does provide support for pausing an animation. It also provides support for a huge array of animation support:

  • Set the desired rate of the animation
  • Get the duration of the animation
  • Automated interpolation of values
  • Set a delay before the animation starts
  • Set whether the animation should play backwards once it’s finished
  • Get the status of the animation (paused, stopped, playing)
  • Properties for all the above for convenient binding
  • Set an ActionEvent to run when the animation finishes
  • Set cue points, like bookmarks, to restart an animation from a convenient

One of the main benefits of JavaFX is its strong information binding. Its downside is that this comprehensive support can decrease runtime performance.

In our case, we’re not interested in delays or playing backwards, so it makes more sense to use the AnimationTimer. This doesn’t extend JavaFX’s Animation, so it’s incredibly lightweight. But, it does give us access to the same timestamp the other classes use.

Start and Stop – and what they actually mean

The high efficiency of the animation timer makes it great for high-intensity tasks game loops. But, if your user pauses the game to bring up the menu, and then finds he’s wandered off a cliff, that’s not great for gameplay. So. we need to be able to pause the timer and start it from where we left off.

The key to why this doesn’t work is the way that the animation timer gets its timestamp.

An animation timer recieves a pulse from the mater timer every frame

The animation timer itself doesn’t time anything. In fact, its only source of information is the animation pulse that cascades through the system every frame.

This is hidden behind the start() and stop() methods, which make it sound like we should be able to stop the clock on our timer. But, if we look at how JavaFX implements start() and stop(), we’ll see why that doesn’t work.

Start()

The animation timer start() method does what it says on the tin – sort of. It registers its pulse receiver with the master timer. That gives the timer access to the clock, but it doesn’t start from zero. Instead, it stars from whatever the JavaFX animation pulse time is on the next pulse. In short, it’s a fairly arbitrary fifteen-digit timestamp.

We can extend the animation timer to include a start time, and subtract that from every timestamp. Now we’re working relative to the start of the animation rather than the arbitrary timestamp JavaFX gave us.

The start method doesn’t give us access to the timestamp, but we can set a flag and assign the start time in the handle() method. We’ll do that below.

Stop()

In fact, one of the easiest mistakes to make when starting to work with timers is to stop() the timer, hoping it will stop the animation, only to find it ‘jumps’ forwards when you restart it.

You’ve probably guessed but stop() doesn’t do what you’d expect either. We might hope it would stop the clock on our animation, like a cassette player, or even re-setting it to zero like a DVD.

Unfortunately, it doesn’t. Instead, it simply unregisters its interest in the timing pulse.

When not registered (stopped) the animation timer will not call the handle() method each frame.

The impact is discontinuity in our animation. If you choose to restart it, the animation timer, your animation will jump to the current timestamp, however far ahead that might be.

Animation Jumps

To see that impact, we’ll test an animation in an imaginary application – the flight of a cannonball. We modify our start() method, so our animation is relative to cannon firing. We also know it’s going to take about 4 seconds for the cannonball to fly through the air.

Whatever equation we’ve set interpolates the flight of the ball, calculating the point in the trajectory with the timestamp.

Two seconds into the animation, our user opens the menu and our program calls stop() on the timer at the top of the arc.

A cannon animation timer is stopped mid-way through the trajectory of the cannon ball

In the background the JavaFX animation MasterTimer carries on. Its responsibility is synchronising any other animations you have. Any fancy menu animations we’d included in our program run on the same timer system, and still work.

When our user closes the menu, our program calls start() again. The animation timer doesn’t ask how long it was paused. Instead, it re-registers its interest in the synchronised timestamp and carries on.

Our interpolator doesn’t think about the pause either, and calculates the position of the cannonball based on the timestamp its given.

So as we restart, the animation jumps, and the cannon ball’s now at the bottom of its arc – it’s trajectory was plumbed directly into the timestamp rather than any custom ‘animation time’.

An animation timer tracking the arc of a cannon ball will jump if stopped and restarted

Pausing an animation

What we’re aiming for is simple:

  • Pause when we want
  • Restart from where we left off

To do that, we’ll nee to keep track of the total pause time for our animation and feed this into our calculation, so the interpolator has the right timestamp to work on.

For this project, we’ll create a concrete instantiation of the AnimationTimer class. We’ll call it PausableAnimationTimer. Firstly, we’ll record the start time of the animation. Then, we’ll expand our class to keep track of the start of the pause event, and update our timer when the pause event ends.

Finally, we’ll give the user the opportunity to define actions to perform every frame again.

Setting animation start time

We can set the animation start time above by overriding the start() method. It doesn’t have access to the timestamp, but we can set a flag and catch it in the next frame using handle().

private long animationStart;

@Override
public void start() {
    super.start();
    restartScheduled = true;
}

@Override
public void handle(long now) {
    if (restartScheduled) {
        animationStart = now;
        restartScheduled = false;
    }
}

Calculating pause duration

When we pause our application, we want to record the start of the pause event using the timestamp of the frame. Again, we’ll schedule our pause with a flag, and catch it in the handle() method. Don’t forget to keep the restartScheduled flag in the handle() method. We’ve just excluded it for brevity.

public void pause() {
    if (!isPaused) {
        pauseScheduled = true;
    }
}

@Override
public void handle(long now) {
    if (pauseScheduled) {
        pauseStart = now;
        isPaused = true;
        pauseScheduled = false;
    }
}

We’ll also set an isPaused flag in the handle method. That prevents a paused animation being re-paused and the pause start being changed accidentally.

Restarting the timer

There are a number of ways to track the pause length for an animation timer. If it’s important to keep track of the overall pause time, we can keep track of it and subtract it from the timestamp. If we need to, we can even keep track of each pause event’s start, finish an duration.

In our case, the simplest way to modify our animation after a pause event is to move the animation start time forward by the duration of the pause. This loses any history of when the animation started, or was paused, but has the same impact of keeping the animation at the right relative point.

Again, we schedule a play request using a public method and handle the flag in our timer method.

public void play() {
    if (isPaused) {
        playScheduled = true;
    }
}

@Override
public void handle(long now) {
    if (playScheduled) {
        animationStart += (now - pauseStart);
        isPaused = false;
        playScheduled = false;
    }
}

Again, don’t forget to keep the other features of the handle method – we still need those. We could also reset the pauseStart to zero, but as we check for pauses prior to scheduling play and pause requests, that’s not strictly necessary.

Using our timer

You might have noticed that we overrode the handle() method when we created a concrete instance of the AnimationTimer. That allowed us to access the timestamp of the frame, whenever we needed it. But, it also means that a user creating a PausableAnimationTimer won’t have to define the method.

Worse, if they choose to override it, they’re remove our work!

To get around this, we create a new abstract method called tick(). We’ll call this method from our handle() method in every frame in cases where our timer isn’t paused.

@Override
public void handle(long now) {
    if (!isPaused) {
        long animDuration = now - animationStart;
        animationDuration.set(animDuration / 1e9);
        tick(animDuration);
    }
}

public abstract void tick(long relativeNow);

Now, any user wanting to instantiate our PausableAnimationTimer will have to override our abstract method.

Conclusions

The AnimationTimer class is an efficient, synchronised class that runs a single method – handle() – every frame. In this project, we’ve added support for pausing this timer, by

  • Defining the animation start time
  • Tracking the animation pause events
  • Modifying the start time to maintain continuity after a pause event
  • Defining a new abstract method to retain the user’s ability to execute code every frame.

As always, the code for this project is on Github here. In this case, we’ve created a simple application that displays the duration of an animation, with controls to pause, play and stop.

The JavaFX Animation timer modified to allow pausing and restarting

We added some extra functionality to our class to restart the timer from zero, so make sure to check it out.