I am trying to show a GIF in a scene on JavaFX, I have it shown on the scene and it plays but it loops back to the beginning before the full GIF is played. The GIF is 216 frames so about 30 seconds long. Is this why it doesn't play the whole thing? Or is there a way to make it play the full thing?
rules.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Stage rulesWindow = new Stage();
Pane r_2 = new Pane(tutorialView);
Scene sc = new Scene(r_2,width/2,height/2);
rulesWindow.setScene(sc);
tutorialView.setFitWidth(width/2);
tutorialView.setFitHeight(height/2);
rulesWindow.show();
}
});
I instantiated tutorialView outside the method, so it is not seen in this part of the code.
This is the full GIF
I can't upload what it plays because the file is too big but if you watch the full GIF it stops where the green circles appear for the first time
I tried playing the gif back on a Windows PC using JRE Temurin 16.0.2 (the java runtime from adoptopenjdk) and JavaFX 17.0.0.1 and that worked (played all the way through).
I then tried running the same test app on JavaFX 16 and it started looping before playing the whole animation (before the green dots started appearing in the animation).
So JavaFX 16 replicates the issue as Michelle described it, and JavaFX 17 fixes the issue due to a bug fix (as was predicted by Jose).
So, if working with long running gifs, ensure you are using JavaFX 17+.
Test code (using the gif from the question):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class GifTest extends Application {
#Override
public void start(Stage stage) {
Scene scene = new Scene(
new StackPane(
new ImageView(
GifTest.class.getResource(
"game.gif"
).toExternalForm()
)
)
);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you would like more control over playback or if you must use an earlier JavaFX version with broken gif playback, you can decode the gif yourself and play it as an animation. See also this sample sprite based playback.
Related
I am very new to javafx, and was getting java.lang.reflect.InvocationTargetException when testing with the code tutorial:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static final String IMAGE_NAME = "groceries.jpg";
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(setupScene(), 300, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("Image Screen");
primaryStage.show();
}
StackPane setupScene() {
StackPane root = new StackPane();
ImageView imageView = new ImageView();
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
Image image = new Image(getClass().getClassLoader().getResource(IMAGE_NAME).toString());
imageView.setImage(image);
root.setPrefSize(250, 250);
imageView.setFitHeight(root.getPrefHeight());
imageView.setFitWidth(root.getPrefWidth());
root.getChildren().add(imageView);
return root;
}
}
The exception was caused by java.lang.NullPointerException in line Image image = new Image(getClass().getClassLoader().getResource(IMAGE_NAME).toString());
The image file is in my project folder, but it doesn't seem to be loaded. I was able to get the image using Image image = new Image(new File(IMAGE_NAME).toURI().toURL().toString()), but when I switched back to Image image = new Image(getClass().getClassLoader().getResource(IMAGE_NAME).toString()), it just never worked.
Does anyone know why my program is behaving like this? Any ideas would be highly appreciated...
Edit: My image file is on the same level of the src folder:
- projectfolder
- groceries.jpg
- src
- Main.java
I'm using IntelliJ JavaFX Application to create the project, everything is in default state.
Image image = new Image(getClass().getClassLoader().getResource(IMAGE_NAME).toString()) and Image image = new Image(new File(IMAGE_NAME).toURI().toURL().toString()) do two different things.
The first will get a resource based off the class loader, which is most commonly used when extracting a resource directly from the jar file. The second is used to load the image from the file system, which is why that works in your case (that's where the file is!)
The getClass().getClassLoader().getResource() line, searchs for the file in the same location where your Main class is.
If you have this image in the src, you just need to add a '/' :
private String theme1Url = getClass().getResource("/groceries.jpg").toExternalForm();
I have a problem with resizing a Stage with the sizeToScene method in javafx.
If I change the root node size above the maximum screen size and call sizeToScene, the window resizes well to the maximum possible screen size.
But if I recall sizeToScene, the scene becomes larger than the stage and the application is cut off.
Sometimes, the first call leads to the same behaviour as the last one described.
I don't know if it is a normal behavior and if there is a workaround solution to adjust the size of the scene to the size of the stage.
The same thing happened on linux and windows.
An example below
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class SceneSizeBug extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Button button = new Button("test");
button.setOnAction(e -> {
button.setPrefSize(2000, 2000);
primaryStage.sizeToScene();
});
button.setPrefSize(1280, 1024);
Scene s = new Scene(button, -1, -1);
primaryStage.setScene(s);
primaryStage.show();
}
}
So I have a couple of Rectangle objects of different sizes, named r1 and r2, that initially appear in the dead center of the screen. They have different colors (say one is red and the other blue) and the smaller one is placed on top of the larger one, so even when they are stacked in the center, the two are still distinguishable. I wanted them to move around and return to their original positions, and thus used TranslateTransition as follows:
tt1 = new TranslateTransition(Duration.millis(20000), button1);
tt1.setByX(100); // moves 100 pixels to the right
tt1.setByY(-100); // moves 100 pixels down
tt1.setCycleCount(20); // oscillates 20 times
tt1.setAutoReverse(true);
tt2 = new TranslateTransition(Duration.millis(20000), button2);
tt2.setByX(-100); // moves 100 pixels to the left
tt2.setByY(100); // moves 100 pixels up
tt2.setCycleCount(20); // oscillates 20 times
tt2.setAutoReverse(true);
During their movement, however, if any of r1 and r2 is pressed by a MouseEvent, I would like them to disappear for a few seconds (say 5 seconds) and come back alive again. Using the fact the background color is completely black, I used FillTransition to achieve that effect:
FillTransition ft1 = new FillTransition(Duration.millis(5000), r1, Color.BLACK, Color.BLACK);
FillTransition ft2 = new FillTransition(Duration.millis(5000), r2, Color.BLACK, Color.BLACK);
By converting the Rectangles from Color.BLACK to Color.BLACK for 5 seconds, it gives an effect that the buttons have disappeared for 5 seconds. Also, I have the following setOnMouseClicked on r1 and r2 so they can disappear when a user input is made:
r1.setOnMouseClicked((MouseEvent t) -> {
ft1.play();
});
r2.setOnMouseClicked((MouseEvent t) -> {
ft2.play();
});
After the two objects have disappeared for 5 seconds, they must reappear in the center, as they did in the beginning, and repeat the same oscillating motion using tt1 and tt2, which I achieved with setOnFinished on ft1 and ft2:
ft1.setOnFinished((ActionEvent event) -> {
r1.setFill(color1); // restore the original color
tt1.play();
});
ft2.setOnFinished((ActionEvent event) -> {
r2.setFill(color2); // restore the original color
tt2.play();
});
The problem is, however, when r1 and r2 reappear, they are positioned not at the center, but rather at the location from which they last disappeared - in other words, the location of their rebirth is where they were at during the last TranslateTransition when a user's MouseEvent is detected. I have tried to modify this by using r1.setX(centerX) and r1.setY(centerY), where centerX and centerY are the original center coordinates used in the beginning, but it could not fix the problem. In fact, when I used r1.getX(), the returned value equaled the original centerX value even when it was conspicuous that r1 was not placed in the center. This gave me a suspicion that TranslateTransition performs its duty without altering the actual getX() values. I have also thought of using ParallelTransition somehow on TranslateTransition and FillTransition, so tt1 could finish while ft1 takes effect, but since then ft1 would start running when tt1 has already been running for some time, it would not provide a feasible solution.
So my question is, if an object's TranslateTransition is interrupted in the middle, how do I restore the object's "original" coordinate, not where the object was last left off when TranslateTransition was interrupted?
p.s. I want to avoid creating new Rectangle objects every time a MouseEvent is detected, because that means all TranslateTransition and FillTransition linked to r1 and r2 must be recreated as well.
Sample Solution
Run the program, the rectangle will start moving. Click on the rectangle and it will disappear momentarily. Shortly after it has disappeared, the rectangle will re-appear at its original start location and start moving along its original trajectory.
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Pauser extends Application {
#Override
public void start(Stage stage) throws Exception {
final Rectangle r1 = new Rectangle(
50, 150, 30, 30
);
final TranslateTransition tt1 = new TranslateTransition(
Duration.seconds(5),
r1
);
tt1.setFromX(0); // start at the layout origin for the node
tt1.setFromY(0); //
tt1.setByX(100); // moves 100 pixels to the right
tt1.setByY(-100); // moves 100 pixels down
tt1.setCycleCount(TranslateTransition.INDEFINITE);
tt1.setAutoReverse(true);
tt1.play();
final PauseTransition pt1 = new PauseTransition(
Duration.seconds(1)
);
pt1.setOnFinished(event -> {
tt1.playFromStart();
r1.setVisible(true);
});
r1.setOnMouseClicked(event -> {
r1.setVisible(false);
tt1.stop();
r1.setTranslateX(0);
r1.setTranslateY(0);
pt1.play();
});
stage.setScene(
new Scene(
new Group(r1),
200, 200
)
);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This just solves your problem for one rectangle. You can stick the solution in a loop to handle multiple rectangles.
Observations
A PauseTransition is used rather than a FillTransition and the visibility of the node is set to false while the pause is running. With a FillTransition the user can still click on the node, even though a node filled with the background color cannot be visibly distinguished from the background. So a FillTransition is probably undesirable.
fromX/fromY properties are set for the translate transition otherwise if you stop and run it from the start it will just use the current translateX/translateY values of the node rather than the origin values of 0/0 which is what you want.
While the node is not visible, there is no need to keep running the TranslateTransition, so the transition is stopped for that duration.
When the translate transition is stopped, it leaves the translateX/translateY co-ordinates at wherever they are currently set for the last animation frame. So a manual call to set translateX/translateY to 0/0 is added.
After the pause transition is complete, a request is made to play the translate transition from the start rather than wherever it was previously paused or stopped.
Understanding Transformations
A node's position on the screen is based on transformations applied to its layout position. The layout position is maintained in the node's layoutX/layoutY properties. But if you apply a translate transformation to a node (as you are implicitly doing in a TranslateTransition), then the node's screen position will be layoutX+translateX / layoutY+translateY.
Background Study
Read the Node documentation, in particular the sections on transformations and bounding rectangles.
Try out this layout bounds demonstration to help understand the concepts.
So the key was using setFromX and setFromY to start from the original coordinate.
It was also key to set manually set the translateX/translateY values to 0/0. If this was not done, the node would flash at its last translated position before starting to move from the origin position. I think this is because there is a frame delay from when you request the animation to start and when it actually starts and uses the fromX/fromY co-ordinates of 0/0. Which is kind of a strange behavior.
This works fine for me. Hopefully it good approach. There could be new class to create extends rectangle with events and transitions in it. This is just sample for two rectangles.
import javafx.animation.FadeTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect1 = new Rectangle (100, 40, 120, 120);
rect1.setArcHeight(42);
rect1.setArcWidth(42);
rect1.setFill(Color.AQUA);
TranslateTransition tt1;
tt1 = new TranslateTransition(Duration.millis(3000), rect1);
tt1.setByX(200);
tt1.setByY(-200);
tt1.setCycleCount(2);
tt1.setAutoReverse(true);
tt1.play();
rect1.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
FadeTransition ft = new FadeTransition(Duration.millis(1000), rect1);
ft.setFromValue(1.0);
ft.setToValue(0.0);
ft.play();
event.consume();
}
});
tt1.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
tt1.play();
FadeTransition ft = new FadeTransition(Duration.millis(1000), rect1);
ft.setToValue(1.0);
ft.play();
}
});
Rectangle rect2 = new Rectangle (100, 40, 80, 80);
rect2.setArcHeight(42);
rect2.setArcWidth(42);
rect2.setFill(Color.YELLOWGREEN);
TranslateTransition tt2;
tt2= new TranslateTransition(Duration.millis(6000), rect2);
tt2.setByX(-200);
tt2.setByY(200);
tt2.setCycleCount(2);
tt2.setAutoReverse(true);
tt2.play();
rect2.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
FadeTransition ft = new FadeTransition(Duration.millis(1000), rect2);
ft.setFromValue(1.0);
ft.setToValue(0.0);
ft.play();
event.consume();
}
});
tt2.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
tt2.play();
FadeTransition ft = new FadeTransition(Duration.millis(1000), rect2);
ft.setToValue(1.0);
ft.play();
}
});
StackPane pane = new StackPane();
pane.setAlignment(Pos.CENTER);
pane.getChildren().addAll(rect1,rect2);
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args){
launch(args);
}
}
i was searching in google for hours and i still cant find the right answer, so i have a last chance to come here and ask.
i'm making school year JAVA FX project. I'm using NetBeans.
I have a point that i can see on the application i have. The problem is: I would like to have a big map (background) and I need to be able to move with my view. For example move by 50 to the right (x).
I have Application where I use Stage, Scene, StackPane.
I heard something about Dimensions in Java, but i can't use it in javafx application. Is there something similar, what can I use in my Application?
Thank you very much.
What I think you are asking for is a Scene with a map (represented as an Image) in the background and controls layered on top of the map to allow interaction with the map at certain positions. Your question is a little unclear, so I'm not exactly sure if that is what you are asking.
If so, here is some sample code to implement that.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
/** Constructs a scene with a pannable Map background. */
public class PannableView extends Application {
private Image backgroundImage;
#Override public void init() {
backgroundImage = new Image("https://www.narniaweb.com/wp-content/uploads/2009/08/NarniaMap.jpg");
}
#Override public void start(Stage stage) {
stage.setTitle("Drag the mouse to pan the map");
// construct the scene contents over a stacked background.
StackPane layout = new StackPane();
layout.getChildren().setAll(
new ImageView(backgroundImage),
createKillButton()
);
// wrap the scene contents in a pannable scroll pane.
ScrollPane scroll = createScrollPane(layout);
// show the scene.
Scene scene = new Scene(scroll);
stage.setScene(scene);
stage.show();
// bind the preferred size of the scroll area to the size of the scene.
scroll.prefWidthProperty().bind(scene.widthProperty());
scroll.prefHeightProperty().bind(scene.widthProperty());
// center the scroll contents.
scroll.setHvalue(scroll.getHmin() + (scroll.getHmax() - scroll.getHmin()) / 2);
scroll.setVvalue(scroll.getVmin() + (scroll.getVmax() - scroll.getVmin()) / 2);
}
/** #return a control to place on the scene. */
private Button createKillButton() {
final Button killButton = new Button("Kill the evil witch");
killButton.setStyle("-fx-base: firebrick;");
killButton.setTranslateX(65);
killButton.setTranslateY(-130);
killButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
killButton.setStyle("-fx-base: forestgreen;");
killButton.setText("Ding-Dong! The Witch is Dead");
}
});
return killButton;
}
/** #return a ScrollPane which scrolls the layout. */
private ScrollPane createScrollPane(Pane layout) {
ScrollPane scroll = new ScrollPane();
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setPannable(true);
scroll.setPrefSize(800, 600);
scroll.setContent(layout);
return scroll;
}
public static void main(String[] args) { launch(args); }
}
For the example use the mouse (or probably touch commands or trackpad scroll gestures - though I haven't a touch screen or trackpad to test it) to drag the map around. Click on the button to "Kill the evil witch".
The solution works by:
Creating an ImageView to hold the background map.
Constructing the scene contents in a StackPane over the stacked background ImageView.
Wrapping the scene in a ScrollPane bound to the scene's size.
Setting properties on the ScrollPane to make it pannable.
I'd like to create a custom loading screen for a JavaFX application. Don't want the user to see the Java coffee cup icon, I want to put my own graphic there!
I've found out how to provide a static image, or even an animated GIF, but I'm more interested in a Flash-like screen where I can specify what the state of the image looks like at certain percentages.
Any ideas?
For JavaFX2, you can set a custom preloader. You have complete control over then scene. I haven't used them personally, but this might be what you want.
http://docs.oracle.com/javafx/2/deployment/preloaders.htm
JavaFX preloader class
I have created a very simple preloader screen using native JavaFX APIs. Here it's explained how to do this: https://docs.oracle.com/javafx/2/deployment/preloaders.htm (old but workable examples) - this is newer and seems to be the same: https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/preloaders.html (Newer page and JavaFX version but I don't see the difference).
The older link is easier to read, because of page formatting.
Main class
package YOUR_PACKAGE_NAME;
import javafx.application.Application;
/**
* Minimal reproducible example (MRE) - Example of a simple JavaFX preloader.
* Java Main class for starting up the JavaFX application with a call to launch MainApplication.
* #author Remzi Cavdar - ict#remzi.info - #Remzi1993
*/
public class Main {
public static void main(String[] args) {
/*
* The following Java system property is important for JavaFX to recognize your custom preloader class.
* Which should extend javafx.application.Preloader.
*/
System.setProperty("javafx.preloader", Preloader.class.getName());
// Launch the main JavaFX application class.
Application.launch(MainApplication.class, args);
}
}
Preloader class
package YOUR_PACKAGE_NAME;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
/**
* Minimal reproducible example (MRE) - Example of a simple JavaFX preloader class.
* #author Remzi Cavdar - ict#remzi.info - #Remzi1993
*/
public class Preloader extends javafx.application.Preloader {
private ProgressBar progressBar;
private Stage stage;
private Scene createPreloaderScene() {
progressBar = new ProgressBar();
BorderPane borderPane = new BorderPane();
borderPane.setCenter(progressBar);
return new Scene(borderPane, 800, 600);
}
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
// I also recommend to set app icon: stage.getIcons().add();
stage.setTitle("YOUR TILE HERE");
stage.setScene(createPreloaderScene());
stage.show();
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
progressBar.setProgress(pn.getProgress());
}
#Override
public void handleStateChangeNotification(StateChangeNotification evt) {
if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
stage.hide();
}
}
}
Testing
Tested on: 01-11-2022
Tested OS: Windows 11 - Version 21H2 (OS Build 22000.1098)
Tested with: OpenJDK 19 - Eclipse Temurin JDK with Hotspot 19.0.1+10 (x64) (See: https://adoptium.net/en-GB/temurin/releases/?version=19)
Tested with JavaFX (OpenJFX) version: OpenJFX 19 (See: https://openjfx.io and repo: https://github.com/openjdk/jfx)
If you're setting things up as shown on This blog entry, it looks like the answer would be 'no' - the loading graphic is just part of the overall options that are passed to the applet. Because this applet could be any java code (not just javaFX), there's no way to tie your custom renderer in.
you should use java timer:
Timer tm= new Timer();
Stage ilk;
int count;
public void check() {
ilk=new Stage();
TimerTask mission;
gorev = new TimerTask() {
#Override
public void run() {
Group root = new Group();
Scene scene;
scene = new Scene(root, 960, 540);
scene.setFill(Color.BLACK);
ilk.setScene(scene);
ilk.setTitle("Splash Screen");
sayac++;
if(count==5){
tm.cancel();
ilk.show();
}
}
};
tm.schedule(mission, 0, 2000);
}
For changing the coffee cup icon:
stage.getIcons().add(new Image("images/myimage.png"));
and here is a reference for a very clear preloader screen out there and awesome css too:
http://docs.oracle.com/javafx/2/best_practices/jfxpub-best_practices.htm