Using Timeline class to Animate a Node – Part 2

As I mentioned in my last post, this post is about controlling the animation by using the methods of the Timeline class. The example from the previous post has been modified to include a GUI. I will not discuss the GUI here because that is the subject of later posts to come. The focus of this post is on using the appropriate methods of the Timeline class and adding listeners to KeyFrames to change the fill color of the text.

The code is given below. I have collapsed the code because it is slightly long.

package blog;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFieldBuilder;
import javafx.scene.effect.Glow;
import javafx.scene.layout.HBox;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextBuilder;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ControlTimelineAnimation extends Application{

	Text hello;

	TextField playFromDuration;
	Button playFrom;
	Button playFromStart;
	Button stop;

	Scene scene;
	HBox buttonHolder;
	Pane pane;
	VBox layout;

	Timeline timeline;
	KeyFrame startFrame;
	KeyFrame endFrame;

	List<paint> colors = new ArrayList<>();
	Random rand = new Random();

	@Override
	public void start(Stage stage) throws Exception {
		colors.add(Color.RED);
		colors.add(Color.GREEN);
		colors.add(Color.BLUE);

		buttonHolder = HBoxBuilder
				.create()
				.alignment(Pos.CENTER_LEFT)
				.build();

		pane = new Pane();

		layout = VBoxBuilder
				.create()
				.alignment(Pos.CENTER)
				.padding(new Insets(10))
				.build();

		scene = SceneBuilder
				.create()
				.height(500)
				.width(500)
				.root(layout)
				.fill(Color.GRAY)
				.build();

		hello = TextBuilder
				.create()
				.text("Hello")
				.stroke(Color.ANTIQUEWHITE)
				.fill(Color.WHEAT)
				.font(Font.font("Ubuntu", 41))
				.effect(new Glow())
				.id("hello")
				.build();

		playFromDuration = TextFieldBuilder
							.create()
							.promptText("From")
							.build();

		playFrom = ButtonBuilder
				   .create()
				   .text("Play From Durtation")
				   .build();

		playFromStart = ButtonBuilder
				        .create()
				        .text("Play From Start")
				        .build();

		stop = ButtonBuilder
				.create()
				.text("Stop")
				.build();

		startFrame = new KeyFrame(new Duration(0),
								  new KeyValue(hello.xProperty(),-200));

		endFrame = new KeyFrame(new Duration(5 * 1000),
				   new EventHandler<ActionEvent>(){
						@Override
						public void handle(ActionEvent arg0) {
							hello.setFill(colors.get(rand.nextInt(colors.size())));
						}
					},
				  new KeyValue(hello.xProperty(),700));

		timeline = TimelineBuilder
				.create()
				.keyFrames(startFrame,endFrame)
				.autoReverse(true)
				.cycleCount(Timeline.INDEFINITE)
				.build();

		layout.getChildren().addAll(pane,buttonHolder);
		pane.getChildren().add(hello);
		buttonHolder.getChildren().addAll(playFromDuration,
										  playFrom,
										  playFromStart,
										  stop);

		final Timeline tl = timeline;
		playFromStart.setOnAction(new EventHandler<ActionEvent>(){
			@Override
			public void handle(ActionEvent event) {
				tl.playFromStart();
			}
		});

		stop.setOnAction(new EventHandler<ActionEvent>(){
			@Override
			public void handle(ActionEvent event) {
				tl.stop();
			}
		});

		playFrom.setOnAction(new EventHandler<ActionEvent>(){
			@Override
			public void handle(ActionEvent event) {
				try{
					int duration = Integer.parseInt(playFromDuration.getText());
					tl.playFrom(new Duration(duration));
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		});

		stage.setScene(scene);
		stage.sizeToScene();
		stage.show();
	}
	public static void main(String[] args) {
		Application.launch("blog.ControlTimelineAnimation");
	}
}

What I have in the code is a single Text node that I will move along the X-axis. I use Timeline class to animate the text and I use KeyFrames to specify what is going to change over a period of five seconds.

I have three Buttons whose names are self explanatory of what they do. I also have a TextField which lets you enter the duration from which the animation should play if you choose not to play the animation from the start.

I then add listeners to the three buttons. In the handle() of playFromStart button, I call the playFromStart() method of the Timeline instance. This causes the animation to play from the beginning. In the handle() of the stop button, I call stop() of the Timeline. This causes the animation to freeze wherever it is. If I call play() now, the animation will resume from where it stopped.

Finally, the TextField lets you enter the duration, in milliseconds, from where you want to play the animation. The idea is to get the text from the textfield, parse it to an int, and then pass it to the playFromDuration() method.

Another thing that I have added is a listener to the KeyFrame. The onFinish listener lets you know when the KeyFrame has finished its role in the animation. In my case, I am setting the fill color of the Text node every time it goes out of the window from the right side. When it comes back, it has a random color that I pick from the ArrayList<>. The listener is passed to the KeyFrame as the second argument to its constructor.

Leave a comment