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

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

In this projection, we'll create a mini animation player, with the ability to suspension, stop and restart the animation timer.

The JavaFX Animation timer modified to allow pausing and restarting
20

How the Timer Works

Merely put, the first() and cease() methods of the animation timer aren't very well named, because they don't do what yous might wait. Nosotros'll start by discussing what they practice accomplish, and and so discuss how to alter our timer to add the functionality we need.

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

Master Timer

If you're interested in how the animation timer sets itself up to receive updates from the JavaFX master timer, nosotros've discussed it here. But, in short, the animation timer is non a timer in-and-of-itself. More accurately, information technology'due south a Receiver. Every frame, JavaFX sends an animation pulse through the arrangement, marked with the same timestamp.

Other classes – Timeline and Transition – extend the JavaFX Animation form. In fact, if you're interested, I've made a comprehensive guide to every element of the JavaFX animation toolkit, so bank check that out too.. This does provide support for pausing an animation. It besides provides support for a huge array of blitheness support:

  • Set the desired charge per unit 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
  • Become the status of the blitheness (paused, stopped, playing)
  • Properties for all the to a higher place for convenient binding
  • Ready an ActionEvent to run when the blitheness finishes
  • Set cue points, like bookmarks, to restart an animation from a convenient

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

In our instance, we're not interested in delays or playing backwards, so it makes more sense to employ the AnimationTimer. This doesn't extend JavaFX's Blitheness, so it'south incredibly lightweight. Merely, it does give us admission to the same timestamp the other classes employ.

Kickoff and Stop – and what they really mean

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

The primal 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 data is the animation pulse that cascades through the system every frame.

This is hidden backside the offset() and stop() methods, which go far audio like we should be able to stop the clock on our timer. But, if nosotros expect at how JavaFX implements start() and end(), we'll see why that doesn't work.

Outset()

The animation timer start() method does what information technology says on the tin – sort of. It registers its pulse receiver with the main timer. That gives the timer access to the clock, simply it doesn't start from zero. Instead, it stars from whatever the JavaFX animation pulse time is on the next pulse. In short, it'south a adequately capricious fifteen-digit timestamp.

Nosotros can extend the blitheness timer to include a beginning time, and decrease that from every timestamp. Now we're working relative to the get-go of the animation rather than the arbitrary timestamp JavaFX gave u.s.a..

The start method doesn't requite united states access to the timestamp, but we can set a flag and assign the start fourth dimension in the handle() method. We'll do that beneath.

Stop()

In fact, one of the easiest mistakes to brand when starting to work with timers is to stop() the timer, hoping it volition stop the animation, but to find it 'jumps' forwards when you restart it.

You've probably guessed only stop() doesn't practice what you lot'd expect either. We might promise it would terminate the clock on our animation, like a cassette player, or even re-setting information technology to zero like a DVD.

Unfortunately, it doesn't. Instead, it merely 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, withal far ahead that might exist.

Animation Jumps

To see that impact, nosotros'll test an animation in an imaginary application – the flying of a cannonball. We alter our get-go() method, and so our animation is relative to cannon firing. Nosotros also know it'southward going to accept about 4 seconds for the cannonball to fly through the air.

Whatever equation we've prepare interpolates the flight of the brawl, calculating the indicate in the trajectory with the timestamp.

Two seconds into the animation, our user opens the menu and our plan calls finish() on the timer at the tiptop of the arc.

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

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

When our user closes the carte du jour, our program calls start() once more. 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 call back 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 brawl'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 blitheness

What we're aiming for is elementary:

  • Pause when nosotros desire
  • Restart from where we left off

To practise that, nosotros'll nee to keep track of the total intermission time for our blitheness and feed this into our calculation, then the interpolator has the correct timestamp to work on.

For this project, we'll create a concrete instantiation of the AnimationTimer class. We'll phone call it PausableAnimationTimer. Firstly, nosotros'll tape the commencement time of the animation. So, nosotros'll expand our grade to go along track of the start of the break issue, and update our timer when the pause event ends.

Finally, we'll give the user the opportunity to define deportment to perform every frame over again.

Setting animation start fourth dimension

We can set up the blitheness beginning fourth dimension above by overriding the beginning() method. It doesn't take access to the timestamp, only we can set a flag and catch information technology in the next frame using handle().

private long animationStart;  @Override public void start() {     super.offset();     restartScheduled = truthful; }  @Override public void handle(long at present) {     if (restartScheduled) {         animationStart = now;         restartScheduled = imitation;     } }

Calculating interruption 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 grab it in the handle() method. Don't forget to proceed the restartScheduled flag in the handle() method. We've only excluded it for brevity.

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

Nosotros'll too set an isPaused flag in the handle method. That prevents a paused animation being re-paused and the suspension start being inverse accidentally.

Restarting the timer

There are a number of means to rails the pause length for an animation timer. If it's of import to keep rail of the overall pause time, we can keep track of it and subtract it from the timestamp. If nosotros need to, nosotros tin can even keep track of each intermission event's start, terminate an elapsing.

In our case, the simplest style to alter our blitheness later on a interruption consequence is to movement the animation start time forward by the duration of the break. This loses whatever history of when the animation started, or was paused, but has the same impact of keeping the animation at the correct relative point.

Again, nosotros schedule a play asking 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 += (at present - pauseStart);         isPaused = simulated;         playScheduled = false;     } }

Once more, don't forget to continue the other features of the handle method – we nonetheless demand those. Nosotros could besides reset the pauseStart to zero, simply as we check for pauses prior to scheduling play and pause requests, that's not strictly necessary.

Using our timer

Yous might have noticed that we overrode the handle() method when nosotros created a concrete instance of the AnimationTimer. That allowed the states to access the timestamp of the frame, whenever we needed it. Only, information technology as well ways that a user creating a PausableAnimationTimer won't have to define the method.

Worse, if they choose to override information technology, they're remove our work!

To go effectually 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 = at present - animationStart;         animationDuration.prepare(animDuration / 1e9);         tick(animDuration);     } }  public abstract void tick(long relativeNow);

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

Conclusions

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

  • Defining the blitheness start time
  • Tracking the animation pause events
  • Modifying the kickoff time to maintain continuity after a pause event
  • Defining a new abstruse method to retain the user's ability to execute lawmaking every frame.

As always, the lawmaking for this project is on Github hither. In this example, we've created a elementary awarding that displays the duration of an blitheness, with controls to intermission, play and stop.

The JavaFX Animation timer modified to allow pausing and restarting

We added some extra functionality to our form to restart the timer from nix, so brand certain to check it out.