After a wrong insert i want to shake a Textfield.
For that i have code a static shake animation
public static void shake(Node node) {
TranslateTransition tt = new TranslateTransition(Duration.millis(50), node);
tt.setByX(10f);
tt.setCycleCount(2);
tt.setAutoReverse(true);
tt.playFromStart();
}
This animation is called in a ChangeListener when the input is wrong.
This works fine but if the user type wrong characters very fast, the TextField is moving to the right.
Is there a way to do a repositioning? Or is there a better way to do this?
Don't create a new transition every time you want to shake the field, otherwise the field will shake while it is already shaking, the outcome of which would be difficult to predict but probably pretty undesirable.
The other thing you need to do is to setFromX(0) for the translate transition. This is actually pretty important because what happens with a translate transition is that, when it stops, the translateX value for the node remains at whatever it was when the transition stopped.
When you invoke playFromStart multiple times while the transition is playing, the transition will be stopped again and then started from the beginning. If you don't have a fromX, then the beginning will be wherever the translateX value last ended up at (which might not be what you want at all and, after shaking, the item might start moving to seemingly random positions on the screen). However, if you have a fromX, then the beginning translateX value will always start from an untranslated position.
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ShakenNotStirred extends Application {
#Override
public void start(Stage stage) throws Exception {
TextField field = new TextField();
Shaker shaker = new Shaker(field);
field.textProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
try {
Integer.parseInt(newValue);
} catch (NumberFormatException e) {
shaker.shake();
}
}
});
StackPane layout = new StackPane(field);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class Shaker {
private TranslateTransition tt;
public Shaker(Node node) {
tt = new TranslateTransition(Duration.millis(50), node);
tt.setFromX(0f);
tt.setByX(10f);
tt.setCycleCount(2);
tt.setAutoReverse(true);
}
public void shake() {
tt.playFromStart();
}
}
}
Same way i thinking.
Thanks jewelsea!
The solution for me is to make the TranslateTransition static and use it inside the static method like this:
private static TranslateTransition tt;
public static TranslateTransition shake(Node node) {
if (tt == null || tt.getNode() != node)
{
tt = new TranslateTransition(Duration.millis(50), node);
}
tt.setByX(10f);
tt.setCycleCount(2);
tt.setAutoReverse(true);
if (tt.getStatus() == Status.STOPPED)
{
tt.playFromStart();
}
return tt;
}
In this way, a shake will only perform when the previous is stopped. And the transition only changed if it is NULL or another Node.
Related
I'm new to JavaFX but have a good understanding of object orientated Java. The following program is a combination of two examples, one that animates and moves shapes , the other animates an object on a mouse button press. Much of the functionality has been removed or changed for my needs.
I've searched through many examples but haven't found one I fully understand regarding moving a sprite and animating on key press.In my program I'm sure that I'm not using the right classes to create the game object, even though with some tweaking I'm sure it could work.
I added some println functions to test the animation. The problem seems to be that the KeyFrame part in the walkSouth animation isn't working/playing.
My question is:
Should I be using different JavaFX classes to create the sprite-sheet animation?
Can this code be easily adapted to function so I can get a better understanding of how JavaFX works.
Here is the main class:
package testing;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private enum UserAction{
NONE,NORTH,SOUTH;
}
private static int APP_W = 200;
private static int APP_H = 200;
private Scene scene;
private UserAction action = UserAction.NONE;
private Timeline timeline = new Timeline();
private boolean running = true;
private int FPS = 60;
private Parent createContent(){
Pane root = new Pane();
root.setPrefSize(APP_W,APP_H);
Image cat_image = new Image("file:res/cata.png");
GameObject obj = new GameObject(cat_image,12,8);
obj.setTranslateX(100);
obj.setTranslateY(100);
KeyFrame frame = new KeyFrame(Duration.millis(1000/FPS), event -> {
if(!running)
return;
switch(action){
case NORTH:
obj.setTranslateY(obj.getTranslateY()-1);
break;
case SOUTH:
obj.walkSouth();
obj.setTranslateY(obj.getTranslateY()+1);
break;
case NONE:
obj.pauseAnimation();
break;
}
});
timeline.getKeyFrames().add(frame);
timeline.setCycleCount(Timeline.INDEFINITE);
root.getChildren().add(obj);
return root;
}
private void restartGame(){
stopGame();
startGame();
}
private void stopGame(){
running = false;
timeline.stop();
}
private void startGame(){
timeline.play();
running = true;
}
public void start(Stage primaryStage) throws Exception{
scene = new Scene(createContent());
scene.setOnKeyPressed(event -> {
switch (event.getCode()) {
case W:
action = UserAction.NORTH;
break;
case S:
action = UserAction.SOUTH;
break;
}
});
scene.setOnKeyReleased(event -> {
switch (event.getCode()) {
case W:
action = UserAction.NONE;
break;
case S:
action = UserAction.NONE;
break;
}
});
primaryStage.setTitle("Simple Animation");
primaryStage.setScene(scene);
primaryStage.show();
startGame();
}
public static void main(String[] args) {
launch(args);
}
}
Here is the GameObject class:
package testing;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
/**
* Created by matt on 26/02/17.
*/
public class GameObject extends Pane {
ObjectImage objectImage;
public GameObject( Image image, int columns, int rows){
objectImage = new ObjectImage(image,columns,rows);
getChildren().setAll(objectImage);
}
public void pauseAnimation(){
getChildren().setAll(objectImage);
objectImage.pauseAnimation();
}
public void walkSouth(){
getChildren().setAll(objectImage);
objectImage.walkSouth();
}
}
class ObjectImage extends ImageView {
private Rectangle2D[] clips;
private double width,height;
private Timeline timeline = new Timeline();
public ObjectImage(Image image,int columns,int rows){
width = image.getWidth()/columns;
height = image.getHeight()/rows;
clips = new Rectangle2D[rows*columns];
int count=0;
for(int row =0;row < rows;row++ )
for(int column = 0 ; column < columns; column++,count++)
clips[count] = new Rectangle2D(width * column, height * row,width,height);
setImage(image);
setViewport(clips[0]);
}
public void pauseAnimation(){
timeline.pause();
}
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
KeyFrame frame = new KeyFrame( Duration.millis(1000/5), event -> {
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
System.out.println("frame test");
});
timeline.setCycleCount(timeline.INDEFINITE);
timeline.getKeyFrames();
timeline.play();
}
}
This is the sprite-sheet image I'm working with
This is the outcome
As hinted by the comment, you did forget to add the frame in the walkSouth method. (Also you set each picture frame in walkSouth method to 200ms. Did you meant to change that?) Here's the code after changing:
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
KeyFrame frame = new KeyFrame( Duration.millis(1000/FPS), event -> {
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
});
timeline.setCycleCount(timeline.INDEFINITE);
timeline.getKeyFrames().add(frame); //This was the offending line.
timeline.play();
}
To answer your first question, yes there are many other options of classes you could use. Two options you could do is use the AnimationTimer or the Transition class. Here's a brief explanation for both (with code samples).
AnimationTimer is called every cycle or frame of rendering, which I believe you might be wanting this one:
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
AnimationTimer tmr = new AnimationTimer() {
#Override
public void handle(long nanoTime)
{
//nanoTime specifies the current time at the beginning of the frame in nano seconds.
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
}
};
tmr.start();
//call tmr.stop() to stop/ pause timer.
}
If however, you don't want an animation to be called each frame, you could extend Transition. A transition has an frac (fractional) value ranging from 0 to 1 that increases with respect to time. I'm not going to go into a whole lot detail, but I'm sure you could look up some more information on the api.
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
Transition trans = new Transition() {
{
setCycleDuration(Duration.millis(1000 / 60.0));
}
#Override
public void interpolate(double frac)
{
if (frac != 1)
return;
//End of one cycle.
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
}
};
trans.setCycleCount(Animation.INDEFINITE);
trans.playFromStart();
//Use trans.pause to pause, trans.stop to stop.
}
Please be patient with me.. I'm very new to Java.
I have two separate JFrames and the first loads the background I want but when I dispose the first JFrame and load the second one it loads with the background from the first.
j1.java
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.ImageIcon;
public class j1 extends JFrame implements KeyListener {
public bg1 img;
public bg2 img2;
public j1() {
lvl1();
}
private JFrame lvl1() {
this.img=new bg1();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
setTitle("lvl1");
setResizable(false);
setSize(600, 600);
setMinimumSize(new Dimension(600, 600));
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().add(img);
pack();
setVisible(true);
return(this);
}
private JFrame lvl2() {
this.img2=new bg2();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
setTitle("lvl2");
setResizable(false);
setSize(600, 600);
setMinimumSize(new Dimension(600, 600));
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().add(img2);
pack();
setVisible(true);
return(this);
}
public void keyPressed(KeyEvent e) { }
public void keyReleased(KeyEvent e) {
if(e.getKeyCode()== KeyEvent.VK_RIGHT) {
lvl1().dispose();
lvl2();
}
}
public void keyTyped(KeyEvent e) { }
public static void main(String[] args) {
new j1();
}
}
bg1.java
import java.awt.Graphics;
import javax.swing.JComponent;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
public class bg1 extends JComponent {
public BufferedImage person;
public BufferedImage background;
public bg1() {
loadImages2();
}
public void loadImages2() {
try {
String personn = "Images/person.gif";
person = ImageIO.read(new File(personn));
String backgroundd = "Images/background2.jpg";
background = ImageIO.read(new File(backgroundd));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, this);
g.drawImage(person, 100, 100, this);
}
public static void main(String[] args) {
new bg1();
}
}
bg2.java is very similar to bg1.java but it has different names for the images and voids.
So you kind of have a series of problems.
First, this is one of the dangers of re-using a frame this way, basically, you never actually remove bg1 from the frame, you just keep adding new instances of the bg2. This means that bg1 is still visible and valid on the frame...
Second, you're calling lvl1() AGAIN before you call lvl2, which is making a new instance of bg1 and adding that to the window and then disposing of it (which does NOT dispose of the components) and then you add a new instance of lvl2 to the frame and the whole thing is just one big mess.
Instead, you should simply be using a CardLayout which will allow you to switch between the individual views more elegantly. See How to Use CardLayout for moer details.
You should also have a look at How to Use Key Bindings instead of using KeyListener
As general rule of thumb, you should avoid overriding JFrame, this has a nasty habit of just confusing the whole thing. Simple create a new instance of a JFrame when you need it and add you components directly to it. Before anyone takes that the wrong way, you'll also want to have a look at The Use of Multiple JFrames, Good/Bad Practice?
Is there a way to set the animation speed of a titledpane? I couldn't find anything.
In fact there are two issues.
First:
The animation of the expanding is faster than the expanding of the content itself. You see that the circle is slightly slower than the bar from the second titledpane is moving down.
Second:
How to change the speed of both of them. I need them at the same speed, because it looks weird.
Here is a small example for testing purposes:
package test;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class TestClass extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox vb = new VBox();
{
TitledPane tp = new TitledPane();
System.out.println(tp.getContextMenu());
tp.setContent(new Circle(100));
tp.setText("asfadf");
tp.expandedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
System.out.println("expand " + newValue);
}
});
vb.getChildren().add(tp);
}
vb.getChildren().add(new Line(0, 0, 100, 0));
{
TitledPane tp = new TitledPane();
tp.setContent(new Circle(100));
tp.setText("asfadf");
tp.expandedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
System.out.println("expand " + newValue);
}
});
vb.getChildren().add(tp);
}
vb.setStyle("-fx-background-color: gray");
Scene scene = new Scene(vb,500,500);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Short answer: There's no API to change the duration.
However, there are two ways to achieve it anyway:
Alternative #1: Reflection
The duration is defined in the static final field com.sun.javafx.scene.control.TitledPaneSkin.TRANSITION_DURATION. Using reflection, you can change its value but this is really bad. Not only because that's a dirty hack, but also because TitledPaneSkin is Oracle internal API that is subject to change anyway. Also, this does not fix the issue with the different speeds:
private static void setTitledPaneDuration(Duration duration) throws NoSuchFieldException, IllegalAccessException {
Field durationField = TitledPaneSkin.class.getField("TRANSITION_DURATION");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(durationField, durationField.getModifiers() & ~Modifier.FINAL);
durationField.setAccessible(true);
durationField.set(TitledPaneSkin.class, duration);
}
Alternative #2: Set your own skin
To be on the safe side, you could create and use your own skin (start by copying the existing one) using titledPane.setSkin(). This way you can also fix the different speed, which is basically caused by linear vs. ease interpolation - but that's quite some work.
Just disable the animation with something like:
TitledPane pane = new TitledPane();
pane.animatedProperty().set(false);
and expanding will be as fast as possible.
I would like to add a text area where the user can see some information that i can see in the console while the progress bar is updating.
How can i add the text area ?
Here is a sample of the code I have used to make the progress bar. Can i add below the progress bar the text area which should fill while computations are mare?
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class ProgressDialogExample extends Application {
static int option = 0;
static ProgressForm pForm = new ProgressForm();
#Override
public void start(Stage primaryStage) {
Button startButton = new Button("Start");
startButton.setOnAction(e -> {
// In real life this task would do something useful and return
// some meaningful result:
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws InterruptedException {
for (int i = 0; i < 10; i++) {
updateProgress(i, 10);
Thread.sleep(200);
}
updateProgress(10, 10);
return null;
}
};
// binds progress of progress bars to progress of task:
pForm.activateProgressBar(task);
// in real life this method would get the result of the task
// and update the UI based on its value:
task.setOnSucceeded(event -> {
startButton.setDisable(false);
});
startButton.setDisable(true);
pForm.getDialogStage().show();
Thread thread = new Thread(task);
thread.start();
});
StackPane root = new StackPane(startButton);
Scene scene = new Scene(root, 350, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
private int closeWindow() {
return option;
}
private static void setCloseWindow() {
// TODO Auto-generated method stub
option = 1;
}
public static class ProgressForm {
private final Stage dialogStage;
private final ProgressBar pb = new ProgressBar();
private final ProgressIndicator pin = new ProgressIndicator();
public ProgressForm() {
dialogStage = new Stage();
dialogStage.initStyle(StageStyle.UTILITY);
// dialogStage.setResizable(false);
// dialogStage.setWidth(400);
// dialogStage.setHeight(300);
// final VBox vbox = new VBox();
dialogStage.initModality(Modality.APPLICATION_MODAL);
final Button exitButton = new Button("Exit");
exitButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
pForm.getDialogStage().close();
setCloseWindow();
}
});
// PROGRESS BAR
pb.setProgress(-1F);
pin.setProgress(-1F);
final HBox hb = new HBox();
hb.setSpacing(5);
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(pb, pin, exitButton);
Scene scene = new Scene(hb);
dialogStage.setScene(scene);
}
public void activateProgressBar(final Task<?> task) {
pb.progressProperty().bind(task.progressProperty());
pin.progressProperty().bind(task.progressProperty());
dialogStage.show();
}
public Stage getDialogStage() {
return dialogStage;
}
}
public static void main(String[] args) {
launch(args);
}
}
Have you heard of Label Labelled before use Label instead of the Text Node you want to use and write your text when you want to, when you want to show your Progressbars do
Label.setGraphic(myprogressbarNode);
Hope it helps
EDIT
I would like to add a text area where the user can see some information that i can see in the console while the progress bar is updating... Can i add below the progress bar the text area which should fill while computations are mare?
and i gave you a solution to it.
suppose you have your ProgressBar pb; and you are adding your ProgressBar to a StackPane you do not have to add the ProgressBar directly but rather add your Text which you will implement that by Label so presume your Label is lb this is how your code will look like
StackPane sp; // parent
ProgressBar pb; // progress indicator
Lable lb; // my text area
sp.getChildren().add(lb);// i have added the text area
lb.setGraphic(pb); we have added the progressbar to the text area
lb.setContentDisplay(ContentDisplay.***);//the *** represents the position you want your graphic
//whether top or left or bottom
//now you are done, your pb will show aligned to your lb, and be updated
//if you want to show text lb.setText("your text");
how implicit is this? :-)
Try adding a listener to the task's messageProperty that will append the text to a TextArea whenever it is changed.
TextArea ta = new TextArea();
public void activateTextArea(final Task<?> task) {
task.messageProperty().addListener((property, oldValue, newValue) -> {
ta.setText(ta.getText() + "\n" + newValue);
});
}
Then put something like this inside the task:
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws InterruptedException {
for (int i = 0; i < 10; i++) {
updateProgress(i, 10);
updateMessage((i*10) + "% done");
Thread.sleep(200);
}
updateProgress(10, 10);
updateMessage("All done!");
return null;
}
};
I'm writing music player and I don't know how to code slider dragging handler to set value after user frees mouse button. When I write simple MouseDragged method dragging brings non estetic "rewinding" sound because mediaplayer changes value every time slider moves. While playing slider automatic changes value by mediaplayer listener to synchronize with track duration. This is what I got so far.
ChangeListener<Duration> timeListener = new ChangeListener<Duration>() {
#Override
public void changed(
ObservableValue<? extends Duration> observableValue,
Duration duration,
Duration current) {
durSlider
.setValue(current
.toSeconds());
}
};
durSlider.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
mediaPlayer.seek(Duration.seconds(durSlider.getValue()));
}
});
The valueChanging property of the slider indicates if the slider is in the process of being changed. It is an observable property, so you can attach a listener directly to it, and respond when the value stops changing:
durSlider.valueChangingProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs, Boolean wasChanging, Boolean isNowChanging) {
if (! isNowChanging) {
mediaPlayer.seek(Duration.seconds(durSlider.getValue()));
}
}
});
This won't change the position of the player if the user clicks on the "track" on the slider, or uses the keyboard to move it. For that, you can register a listener with the value property. You need to be careful here, because the value is also going to change via your time listener. In theory, the time listener should set the value of the slider, and then that should cause an attempt to set the current time of the player to the exact value it already has (which would result in a no-op). However, rounding errors will likely result in a lot of small adjustments, causing the "static" you are observing. To fix this, only move the media player if the change is more than some small minimum amount:
private static double MIN_CHANGE = 0.5 ; //seconds
// ...
durSlider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> obs, Number oldValue, Number newValue) {
if (! durSlider.isValueChanging()) {
double currentTime = mediaPlayer.getCurrentTime().toSeconds();
double sliderTime = newValue.doubleValue();
if (Math.abs(currentTime - sliderTime) > 0.5) {
mediaPlayer.seek(newValue.doubleValue());
}
}
}
});
Finally, you don't want your time listener to move the slider if the user is trying to drag it:
ChangeListener<Duration> timeListener = new ChangeListener<Duration>() {
#Override
public void changed(
ObservableValue<? extends Duration> observableValue,
Duration duration,
Duration current) {
if (! durSlider.isValueChanging()) {
durSlider.setValue(current.toSeconds());
}
}
};
Here's a complete example (using lambdas for brevity):
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
import javafx.util.Duration;
public class VideoPlayerTest extends Application {
private static final String MEDIA_URL =
"http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv";
private static final double MIN_CHANGE = 0.5 ;
#Override
public void start(Stage primaryStage) {
MediaPlayer player = new MediaPlayer(new Media(MEDIA_URL));
MediaView mediaView = new MediaView(player);
Slider slider = new Slider();
player.totalDurationProperty().addListener((obs, oldDuration, newDuration) -> slider.setMax(newDuration.toSeconds()));
BorderPane root = new BorderPane(mediaView, null, null, slider, null);
slider.valueChangingProperty().addListener((obs, wasChanging, isChanging) -> {
if (! isChanging) {
player.seek(Duration.seconds(slider.getValue()));
}
});
slider.valueProperty().addListener((obs, oldValue, newValue) -> {
if (! slider.isValueChanging()) {
double currentTime = player.getCurrentTime().toSeconds();
if (Math.abs(currentTime - newValue.doubleValue()) > MIN_CHANGE) {
player.seek(Duration.seconds(newValue.doubleValue()));
}
}
});
player.currentTimeProperty().addListener((obs, oldTime, newTime) -> {
if (! slider.isValueChanging()) {
slider.setValue(newTime.toSeconds());
}
});
Scene scene = new Scene(root, 540, 280);
primaryStage.setScene(scene);
primaryStage.show();
player.play();
}
public static void main(String[] args) {
launch(args);
}
}