JavaFX : How to detect if a key is being held down? - animation

I'm working with Timelines and was hoping to hook up some KeyPress events to the stage that could alter the way the timeline changes the properties over the course it runs.
I know how to differentiate between what key was pressed and for what keys I want to listen, but need to know how I can determine if a key has just been pressed once, like typing, or if a key is being held down for a longer period of time, so that I can have the program make more rapid adjustments the longer the key is held down.

When a key is being held down, you keep on getting KEY_PRESSED events. You can count how many presses of the same key you get in a row:
SimpleIntegerProperty aCount = new SimpleIntegerProperty(0);
SimpleIntegerProperty bCount = new SimpleIntegerProperty(0);
KeyCombination a = new KeyCodeCombination(KeyCode.A);
KeyCombination b = new KeyCodeCombination(KeyCode.B);
scene.setOnKeyPressed(ke -> {
aCount.set(a.match(ke) ? aCount.get() + 1 : 0);
bCount.set(b.match(ke) ? bCount.get() + 1 : 0);
});
scene.setOnKeyReleased(ke -> {
if(a.match(ke)) { aCount.set(0); }
else if(b.match(ke)) { bCount.set(0); }
});
Here is a simple test application:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class KeySpeedTest extends Application {
#Override
public void start(Stage primaryStage) {
SimpleIntegerProperty aCount = new SimpleIntegerProperty(0);
SimpleIntegerProperty bCount = new SimpleIntegerProperty(0);
KeyCombination a = new KeyCodeCombination(KeyCode.A);
KeyCombination b = new KeyCodeCombination(KeyCode.B);
Label aLabel = new Label();
Label bLabel = new Label();
aLabel.textProperty().bind(Bindings.concat(" A: ", aCount));
bLabel.textProperty().bind(Bindings.concat(" B: ", bCount));
HBox root = new HBox(aLabel, bLabel);
Scene scene = new Scene(root, 300, 250);
scene.setOnKeyPressed(ke -> {
aCount.set(a.match(ke) ? aCount.get() + 1 : 0);
bCount.set(b.match(ke) ? bCount.get() + 1 : 0);
});
scene.setOnKeyReleased(ke -> {
if(a.match(ke)) { aCount.set(0); }
else if(b.match(ke)) { bCount.set(0); }
});
primaryStage.setScene(scene);
primaryStage.setTitle("Key Speed Test");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

Exception occurred when iterating though for loop that includes mouseEvent action

the following exception message appears when I iterate using NORMAL for-loop that includes mouseEvent action through radioButtons.
Message:
Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException
But when I iterate using for-each loop, there is no problem!
please help me, thank you
Concerned code part:
HBox p1 = new HBox();
RadioButton Red = new RadioButton("RED");
RadioButton Blue = new RadioButton("Blue");
RadioButton Black = new RadioButton("Black");
ToggleGroup tg = new ToggleGroup();
Red.setToggleGroup(tg);
Blue.setToggleGroup(tg);
Black.setToggleGroup(tg);
RadioButton [] array = {Red,Blue,Black};
p1.getChildren().addAll(Red,Blue,Black);
i = 0;
for(i = 0; i< array.length; i++){
array[i].setOnAction(e->{
System.out.println(array[i].getText());
});
}
I'm guessing, because your question is incomplete, that you made the loop index i an instance variable, for some reason. I.e. you have something like:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Demo extends Application {
// Guessing:
private int i ;
#Override
public void start(Stage primaryStage) throws Exception {
HBox p1 = new HBox();
RadioButton red = new RadioButton("RED");
RadioButton blue = new RadioButton("Blue");
RadioButton black = new RadioButton("Black");
ToggleGroup tg = new ToggleGroup();
red.setToggleGroup(tg);
blue.setToggleGroup(tg);
black.setToggleGroup(tg);
RadioButton[] array = { red, blue, black };
p1.getChildren().addAll(red, blue, black);
i = 0;
for (i = 0; i < array.length; i++) {
array[i].setOnAction(e -> {
System.out.println(array[i].getText());
});
}
Scene scene = new Scene(p1, 400, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Here i is a property of the Demo instance (i.e. the application instance on which start() is invoked). In the start() method, i is initialized to 0 and then incremented each time you iterate the for loop. The for loop exits when i is no longer less than array.length (which is 3), so when the for loop exits, i==3.
Consequently, when you press one of the buttons, the code
System.out.println(array[i].getText());
is executed. The value of i hasn't changed since the completion of the for loop, so this is equivalent to
System.out.println(array[3].getText());
and this throws an IndexOutOfBoundsException, because the value indexes in the array are 0, 1, and 2. Indeed, the complete error message is
Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
Instead, you need to use a local variable for the index in the loop:
for (int i = 0; i < array.length; i++) {
array[i].setOnAction(e -> { /* ... */ });
}
The problem now is that the lambda expression in the event handler cannot access the local variable i because it is not final (or "effectively final"). The solution to this is to "capture" the value of i on each iteration of the loop in a final variable:
for (int i = 0; i < array.length; i++) {
final int index = i ;
array[i].setOnAction(e -> {
System.out.println(array[index].getText());
});
}
Of course, you don't actually need the index; you just need the element of the array, so you could capture that instead:
for (int i = 0; i < array.length; i++) {
final RadioButton button = array[i] ;
button.setOnAction(e -> {
System.out.println(button.getText());
});
}
This code is completely identical (in the sense that the compiler converts the following to the same thing) to:
for (RadioButton button : array) {
button.setOnAction(e -> {
System.out.println(button.getText());
});
}
which is by far the preferred form for a for loop.
Here's a fully-cleaned and working version of the code:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Demo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
HBox p1 = new HBox();
RadioButton red = new RadioButton("RED");
RadioButton blue = new RadioButton("Blue");
RadioButton black = new RadioButton("Black");
ToggleGroup tg = new ToggleGroup();
RadioButton[] buttons = { red, blue, black };
tg.getToggles().setAll(buttons);
p1.getChildren().addAll(buttons);
for (RadioButton button : buttons) {
button.setOnAction(e -> {
System.out.println(button.getText());
});
}
Scene scene = new Scene(p1, 400, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}

Whenever I use the Text object in JavaFX, all object overlap the text, even subsequent text objects

Update: I am using JavaFX 13
This problem happens across all my JavaFX programs, but this is one example. A user is supposed to be able to select options for a custom milkshake then get a receipt when they checkout. All of the object referencing and MVC works as it is supposed to, but when displaying the information using Text objects (javafx.scene.text.Text), they are overlapped by other objects around them. In the second and third images, I have the occurrences circled.
First launch of the program. The first image
Here is the code for the text pane in the second image:
After adding a few selections. The second Image
import Controllers.Controller;
import Entities.Milkshake;
import Interfaces.Observer;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import java.util.ArrayList;;
public class TextSummaryView extends AnchorPane implements Observer<Milkshake> {
private Milkshake model;
private Controller controller;
private VBox iceCreamOptions;
private VBox toppingOptions;
private Text totalCost;
private final double minHeight = 100.0;
private final double minWidth = 50.0;
public TextSummaryView() {
super();
iceCreamOptions = new VBox();
toppingOptions = new VBox();
totalCost = new Text("$0.00");
Button checkout = new Button("Checkout");
checkout.setOnAction(e -> controller.checkout());
Text iceCreamText = new Text("Click '-' to remove\nCurrent Ice Cream Selections:");
Text toppingText = new Text("Current Topping Selections");
iceCreamOptions.getChildren().add(iceCreamText);
toppingOptions.getChildren().add(toppingText);
this.getChildren().addAll(checkout, iceCreamOptions, toppingOptions, totalCost);
// Setting position of elements
setBottomAnchor(checkout, this.getHeight()-5.0);
setRightAnchor(checkout, this.getWidth()-5.0);
setLeftAnchor(totalCost, 5.0);
setBottomAnchor(totalCost, this.getHeight()-5.0);
setTopAnchor(iceCreamOptions, 5.0);
setLeftAnchor(iceCreamOptions, 5.0);
setTopAnchor(toppingOptions, Math.max(40.0, iceCreamOptions.getHeight() + 10.0));
setLeftAnchor(toppingOptions, 5.0);
this.setMinHeight(minHeight);
this.setMinWidth(minWidth);
}
/**
* Sets what the observer will watch
* #param obs An object that extends the Observable interface
*/
public void setTarget(Milkshake obs){
this.model = obs;
}
/**
* Removes the object from target
*/
public void removeTarget(){
this.model = null;
}
/**
* Called by observables to update the observer's data
*/
public void update(){
this.iceCreamOptions.getChildren().remove(1, this.iceCreamOptions.getChildren().size());
this.toppingOptions.getChildren().remove(1, this.toppingOptions.getChildren().size());
// Get Data
ArrayList<String> iceCreams = model.getIceCreams();
ArrayList<String> toppings = model.getToppings();
// Update the ice cream selections
int iceCreamIndex = 0;
for (String ic: iceCreams) {
AnchorPane selection = new AnchorPane();
Button removeButton = new Button("-");
Text iceCream = new Text(ic + ":");
Text cost = new Text("$1.00");
final int iceIndexFin = iceCreamIndex;
removeButton.setOnAction(e -> controller.removeIceCream(iceIndexFin));
selection.getChildren().addAll(removeButton, iceCream, cost);
setLeftAnchor(removeButton, 5.0);
setLeftAnchor(iceCream, 50.0);
setTopAnchor(iceCream, selection.getHeight()/3);
setLeftAnchor(cost, selection.getWidth()-5.0);
setTopAnchor(cost, selection.getHeight()/3);
this.iceCreamOptions.getChildren().add(selection);
iceCreamIndex ++;
}
// Update the topping selections
int toppingIndex = 0;
for (String top: toppings) {
AnchorPane selection = new AnchorPane();
Button removeButton = new Button("-");
Text topping = new Text(top + ":");
Text cost = new Text("$0.50");
final int topIndexFin = toppingIndex;
removeButton.setOnAction(e -> controller.removeTopping(topIndexFin));
selection.getChildren().addAll(removeButton, topping, cost);
setLeftAnchor(removeButton, 5.0);
setLeftAnchor(topping, 50.0);
setTopAnchor(topping, selection.getHeight()/3);
setLeftAnchor(cost, selection.getWidth()-5.0);
setTopAnchor(cost, selection.getHeight()/3);
this.toppingOptions.getChildren().add(selection);
toppingIndex ++;
}
setTopAnchor(iceCreamOptions, 5.0);
setLeftAnchor(iceCreamOptions, 5.0);
setTopAnchor(toppingOptions, Math.max(40.0, iceCreamOptions.getHeight() + 10.0));
setLeftAnchor(toppingOptions, 5.0);
this.totalCost.setText( "$" + this.model.getCost() + "0");
}
public void setController(Controller controller){
this.controller = controller;
}
}
Here is the code for the checkout screen. Pressing the checkout button. All are different text objects in a VBox. The third image
import Entities.Milkshake;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import java.util.ArrayList;
public class CheckoutView extends Stage {
VBox root = new VBox();
public CheckoutView(Milkshake ms) {
super();
// Get Data
ArrayList<String> iceCreams = ms.getIceCreams();
ArrayList<String> toppings = ms.getToppings();
// Update the ice cream selections
int iceCreamIndex = 0;
for (String ic: iceCreams) {
AnchorPane selection = new AnchorPane();
Text iceCream = new Text(ic + ":");
Text cost = new Text("$1.00");
final int iceIndexFin = iceCreamIndex;
selection.getChildren().addAll(iceCream, cost);
AnchorPane.setLeftAnchor(iceCream, 50.0);
AnchorPane.setLeftAnchor(cost, this.getWidth()-5.0);
this.root.getChildren().add(selection);
iceCreamIndex ++;
}
// Update the topping selections
int toppingIndex = 0;
for (String top: toppings) {
AnchorPane selection = new AnchorPane();
Text topping = new Text(top + ":");
Text cost = new Text("$0.50");
selection.getChildren().addAll(topping, cost);
AnchorPane.setLeftAnchor(topping, 50.0);
AnchorPane.setLeftAnchor(cost, this.getWidth()-5.0);
this.root.getChildren().add(selection);
toppingIndex ++;
}
AnchorPane total = new AnchorPane();
Text costTotal = new Text("$" +ms.getCost() + "0");
total.getChildren().add(costTotal);
AnchorPane.setLeftAnchor(total, this.getMaxWidth()-costTotal.getX());
BorderPane exitOptions = new BorderPane();
Button pay = new Button("Finish and Pay");
Button ret = new Button("Return to Order");
pay.setOnAction(e -> Platform.exit());
ret.setOnAction(e -> this.close());
exitOptions.setLeft(ret);
exitOptions.setRight(pay);
root.getChildren().add(exitOptions);
this.setTitle("Checkout");
this.setScene(new Scene(root));
}
public static void checkout(Milkshake ms) {
CheckoutView check = new CheckoutView(ms);
check.show();
}
}
Thank you in advance for all your help! :)

speak text as Type

Javafx:How can text-to speech is done on animated text; I have applied a typewriter effect on text to make animated text, and now i want that it will speak word by word as typed. P.S. for Text-to-Speech iam using the "FreeTTS is a speech synthesis engine"
here is code snippet of my project
public void AnimattedTextToSpeech()
{
// Text to Speech
Voice voice;
VoiceManager vm=VoiceManager.getInstance();
voice=vm.getVoice("kevin16");
voice.allocate();
// TypeWritter Effect to the text
String str="Welcome! This is the Lesson number one";
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline();
KeyFrame keyFrame = new KeyFrame(Duration.seconds(0.1), event2 -> {
if (i.get() > str.length()) {
timeline.stop();
} else {
textArea.setText(str.substring(0, i.get()));
i.set(i.get() + 1);
textArea.requestFocus();
textArea.end();
}
});
voice.speak(str);
timeline.getKeyFrames().add(keyFrame);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
But it is speaking every character as it is typing. But i want it speak word by word.
This works but it seems like you need to run the speech on a different thread.
import com.sun.speech.freetts.Voice;
import com.sun.speech.freetts.VoiceManager;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author blj0011
*/
public class FreeTTTS extends Application
{
#Override
public void start(Stage primaryStage)
{
TextArea textArea = new TextArea();
// Text to Speech
Voice voice;
VoiceManager vm = VoiceManager.getInstance();
voice = vm.getVoice("kevin16");
voice.allocate();
// TypeWritter Effect to the text
String str = "Welcome! This is the Lesson number one";
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline();
AtomicInteger startIndex = new AtomicInteger();
AtomicInteger endIndex = new AtomicInteger();
KeyFrame keyFrame = new KeyFrame(Duration.seconds(0.1), event2 -> {
if (i.get() >= str.length()) {
timeline.stop();
startIndex.set(endIndex.get());
endIndex.set(i.get());
String word = str.substring(startIndex.get(), endIndex.get()).trim().replaceAll("[^a-zA-Z ]", "");
System.out.println(word);
voice.speak(word);
}
else {
textArea.appendText(Character.toString(str.charAt(i.get())));
if (str.charAt(i.get()) == ' ') {
if (endIndex.get() == 0) {
endIndex.set(i.get());
String word = str.substring(startIndex.get(), endIndex.get()).trim().replaceAll("[^a-zA-Z ]", "");
System.out.println(word);
voice.speak(word);
}
else {
startIndex.set(endIndex.get());
endIndex.set(i.get());
String word = str.substring(startIndex.get(), endIndex.get()).trim().replaceAll("[^a-zA-Z ]", "");
System.out.println(word);
voice.speak(word);
}
}
i.set(i.get() + 1);
}
});
//voice.speak(str);
StackPane root = new StackPane(textArea);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
timeline.getKeyFrames().add(keyFrame);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
//voice.speak("Hello World");
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
I imagine you should divide text into strings, and start TTS the moment you start TypeWritter effect for each string.
Like this:
String str1 = "Welcome! This is the Lesson number one";
String[] temp = str1.split(" ");
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline();
for (String str : temp) {
KeyFrame keyFrame = new KeyFrame(Duration.seconds(0.1), event2 -> {
if (i.get() > str.length()) {
timeline.stop();
} else {
textArea.setText(str.substring(0, i.get()));
i.set(i.get() + 1);
textArea.requestFocus();
textArea.end();
}
});
voice.speak(str);
timeline.getKeyFrames().add(keyFrame);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}

JavaFX - navigation panel

i am trying to make a navigation panel sliding out from left side. I have managed an animation already but i cannot solve one thing and that is green empty space left in left area of the program's window. Does anyone have an idea how to get rid of the green space on the left? Thanks
My code looks like this (just copy and paste, it should work)
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application
{
BorderPane slider;
BorderPane mainPane;
Rectangle clip;
Button show;
boolean displayed;
#Override
public void start(Stage primaryStage) throws Exception {
show = new Button("Press");
displayed = false;
mainPane = new BorderPane();
mainPane.setPrefSize(500,200);
slider = new BorderPane(); slider.setPrefHeight(200);
mainPane.setStyle("-fx-background-color: chartreuse");
slider.setStyle("-fx-background-color: aqua");
clip = new Rectangle(0,200);
slider.setPrefWidth(0);
slider.setClip(clip);
slider.setCenter(new Button("Hello"));
mainPane.setTop(show);
mainPane.setCenter(new TextArea("some text"));
mainPane.setLeft(slider);
show.setOnAction(event -> {
if (displayed)
{
Timeline tm = new Timeline();
KeyValue kv1 = new KeyValue(slider.translateXProperty(), -100);
KeyValue kv2 = new KeyValue(slider.prefWidthProperty(), 0);
KeyValue kv3 = new KeyValue(clip.widthProperty(), 0);
KeyFrame kf = new KeyFrame(Duration.millis(200), kv1, kv2, kv3);
tm.getKeyFrames().addAll(kf);
tm.play();
displayed = false;
}
else
{
Timeline tm = new Timeline();
KeyValue kv1 = new KeyValue(slider.translateXProperty(), 0);
KeyValue kv2 = new KeyValue(slider.prefWidthProperty(), 100);
KeyValue kv3 = new KeyValue(clip.widthProperty(), 100);
KeyFrame kf = new KeyFrame(Duration.millis(200), kv1, kv2, kv3);
tm.getKeyFrames().addAll(kf);
tm.play();
displayed = true;
}
});
Scene sc = new Scene(mainPane);
primaryStage.setScene(sc);
primaryStage.show();
}
}
You can add the slider (left child of the mainPane) when you are press on the button instead of adding it before hand. Then, when you press the button again, you can remove the slider after the transition is completed.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application
{
BorderPane slider;
BorderPane mainPane;
Rectangle clip;
Button show;
boolean displayed;
#Override
public void start(Stage primaryStage) throws Exception {
show = new Button("Press");
displayed = false;
mainPane = new BorderPane();
mainPane.setPrefSize(500,200);
slider = new BorderPane();
slider.setPrefHeight(200);
mainPane.setStyle("-fx-background-color: chartreuse");
slider.setStyle("-fx-background-color: aqua");
clip = new Rectangle(0,200);
slider.setPrefWidth(0);
slider.setClip(clip);
slider.setCenter(new Button("Hello"));
mainPane.setTop(show);
mainPane.setCenter(new TextArea("some text"));
show.setOnAction(event -> {
if (displayed) {
Timeline tm = new Timeline();
KeyValue kv1 = new KeyValue(slider.translateXProperty(), -100);
KeyValue kv2 = new KeyValue(slider.prefWidthProperty(), 0);
KeyValue kv3 = new KeyValue(clip.widthProperty(), 0);
KeyFrame kf = new KeyFrame(Duration.millis(200), kv1, kv2, kv3);
tm.getKeyFrames().addAll(kf);
tm.play();
displayed = false;
tm.setOnFinished(e -> mainPane.setLeft(null));
} else {
mainPane.setLeft(slider);
Timeline tm = new Timeline();
KeyValue kv1 = new KeyValue(slider.translateXProperty(), 0);
KeyValue kv2 = new KeyValue(slider.prefWidthProperty(), 100);
KeyValue kv3 = new KeyValue(clip.widthProperty(), 100);
KeyFrame kf = new KeyFrame(Duration.millis(200), kv1, kv2, kv3);
tm.getKeyFrames().addAll(kf);
tm.play();
displayed = true;
}
});
Scene sc = new Scene(mainPane);
primaryStage.setScene(sc);
primaryStage.show();
}
}
You can also re-use the timline instead of creating a new one every time :)
I know this is an old thread, but there is a great JavaFX library available that would help here: controlsFX. In particular the HiddenSidesPane Control object.

JavaFX Animation: Move Shape (Star) randomly

My problem is, that after every keyframe the x- and y-Position of the rectangle should change by random.
Right now only when I start the program, the rectangles position is set by random, but not in the animation itself.
How can I do this, thanks a lot...
public class TimeLines extends Application {
private Rectangle rectBasicTimeline;
private Timeline timeline;
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Do Animation");
int x = new Random().nextInt(500);
int y = new Random().nextInt(400);
rectBasicTimeline = new Rectangle(x, y, 100, 50);
rectBasicTimeline.setFill(Color.RED);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
final Timeline timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
final KeyValue kx = new KeyValue(rectBasicTimeline.xProperty(), x + 200);
final KeyValue ky = new KeyValue(rectBasicTimeline.yProperty(), y + 200);
final KeyValue kScale = new KeyValue(rectBasicTimeline.scaleXProperty(), 2);
final KeyValue kFade = new KeyValue(rectBasicTimeline.opacityProperty(), 0);
final KeyFrame kf = new KeyFrame(Duration.millis(3000), kx, ky, kScale, kFade);
timeline.getKeyFrames().add(kf);
timeline.play();
}
});
AnchorPane root = new AnchorPane();
root.getChildren().addAll(btn, rectBasicTimeline);
Scene scene = new Scene(root, 800, 600);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I agree with #Roland, no need to create multiple Timeline. Reclicking the button just restarts the timeline with new KeyFrame:
public class StarFall extends Application
{
private Polygon star;
private Timeline timeline;
private final double shs = 5.0; // Star Hand Size
private final Random random = new Random();
#Override
public void start( Stage primaryStage )
{
// init shape
Pos initPos = getRandomPos();
star = new Polygon();
star.setLayoutX( initPos.x );
star.setLayoutY( initPos.y );
star.setFill( Color.YELLOW );
// the shape
star.getPoints().addAll( new Double[]
{
0.0, shs * 3,
shs * 2, shs * 2,
shs * 3, 0.0,
shs * 4, shs * 2,
shs * 6, shs * 3,
shs * 4, shs * 4,
shs * 3, shs * 6,
shs * 2, shs * 4
} );
// init timeline
timeline = new Timeline();
timeline.setCycleCount( Timeline.INDEFINITE );
timeline.setAutoReverse( true );
// init button
Button btnStart = new Button( "Do Animation" );
btnStart.setOnAction( ( e ) -> playNextKeyFrame() );
Button btnStop = new Button( "Stop Animation" );
btnStop.setLayoutX( 200 );
btnStart.setLayoutX( 0 );
btnStop.setOnAction( ( e ) -> timeline.stop() );
// init scene with root
AnchorPane root = new AnchorPane( btnStart, btnStop, star );
Scene scene = new Scene( root, 800, 600 );
// show
primaryStage.setScene( scene );
primaryStage.show();
}
private void playNextKeyFrame()
{
// generate next random start and end positions for star
Pos startPos = getRandomPos();
Pos endPos = getRandomPos();
// initial values (resetting)
star.setLayoutX( startPos.x );
star.setLayoutY( startPos.y );
star.setScaleX( 1 );
star.setScaleY( 1 );
star.setOpacity( 1 );
// target values
KeyValue kx = new KeyValue( star.layoutXProperty(), endPos.x );
KeyValue ky = new KeyValue( star.layoutYProperty(), endPos.y );
KeyValue kScaleX = new KeyValue( star.scaleXProperty(), 3 );
KeyValue kScaleY = new KeyValue( star.scaleYProperty(), 3 );
KeyValue kFade = new KeyValue( star.opacityProperty(), 0.0 );
// delay animation before start. Use this instead of THread.sleep() !!
timeline.setDelay( Duration.millis( random.nextInt( 2000 ) + 100 ) );
// restart timeline with new values
timeline.stop();
timeline.getKeyFrames().clear();
timeline.getKeyFrames().add( new KeyFrame( Duration.millis( 3000 ),
( e ) -> playNextKeyFrame(), kx, ky, kFade, kScaleX, kScaleY ) );
timeline.play();
}
private Pos getRandomPos()
{
int x = random.nextInt( 500 );
int y = random.nextInt( 400 );
Pos p = new Pos();
p.x = x + 200;
p.y = y + 200;
return p;
}
private class Pos
{
int x;
int y;
}
public static void main( String[] args )
{
launch( args );
}
}
You initialize the random numbers and hence the location once and use it all the time. What you need to do is to perform the animation cycle once and then create a new animation when the current one finishes.
Example:
import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TimeLines extends Application {
private Rectangle rectBasicTimeline;
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Do Animation");
int x = new Random().nextInt(500);
int y = new Random().nextInt(400);
rectBasicTimeline = new Rectangle(x, y, 100, 50);
rectBasicTimeline.setFill(Color.RED);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
play();
}
});
AnchorPane root = new AnchorPane();
root.getChildren().addAll(btn, rectBasicTimeline);
Scene scene = new Scene(root, 800, 600);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
private void play() {
double x = new Random().nextInt(500);
double y = new Random().nextInt(400);
final Timeline timeline = new Timeline();
// cycle count = 2 because of autoreverse
timeline.setCycleCount(2);
timeline.setAutoReverse(true);
final KeyValue kx = new KeyValue(rectBasicTimeline.xProperty(), x + 200);
final KeyValue ky = new KeyValue(rectBasicTimeline.yProperty(), y + 200);
final KeyValue kScale = new KeyValue(rectBasicTimeline.scaleXProperty(), 2);
final KeyValue kFade = new KeyValue(rectBasicTimeline.opacityProperty(), 0);
final KeyFrame kf = new KeyFrame(Duration.millis(1000), kx, ky, kScale, kFade);
timeline.getKeyFrames().add(kf);
timeline.setOnFinished(e -> {
// create new animation after this animation finishes
play();
});
timeline.play();
}
/**
* #param args
* the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I wouldn't suggest this approach though, e. g. you run into problems with multiple timelines when you click the button multiple times. But I have no information what you're trying to do, so I'll leave it at that.

Resources