I am trying to develop a slot machine reel application. I have a custom reel pane which adds children vertically. When a spin button is clicked children have to move and when the last child reaches a boundary it has to shift its position above the first child. What I did is shown below.
public class ReelPane extends Pane {
Timeline timeline = new Timeline();
@Override
protected void layoutChildren() {
List<Node> managed = getChildren();
double y = 0;
for (Node node : managed) {
node.setLayoutX(0);
node.setLayoutY(y);
y += node.getBoundsInLocal().getHeight();
}
}
public void spin() {
List<Node> managed = getChildren();
double dy = 4;
for (Node node : managed) {
timeline.getKeyFrames().addAll(new KeyFrame(Duration.millis(2000),new KeyValue(node.layoutYProperty(),node.getLayoutY()+dy)));
if(node.getLayoutY()>=600){
node.setLayoutY(-50);
}
}
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
}
fxml file
<?import javafx.scene.control.Button?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.Pane?>
<?import sample.ReelPane?>
<Pane stylesheets="@css/slot.css"
xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx"
fx:controller="sample.Controller">
<ReelPane fx:id="reel" styleClass="container">
<ImageView fitHeight="100" fitWidth="100">
<Image url="/sample/resources/apple.png"/>
</ImageView>
<ImageView fitHeight="100" fitWidth="100">
<Image url="/sample/resources/diamond.png"/>
</ImageView>
<ImageView fitHeight="100" fitWidth="100">
<Image url="/sample/resources/glass.png"/>
</ImageView>
<ImageView fitHeight="100" fitWidth="100">
<Image url="/sample/resources/grape.png"/>
</ImageView>
<ImageView fitHeight="100" fitWidth="100">
<Image url="/sample/resources/star.png"/>
</ImageView>
</ReelPane>
<Button fx:id="spin" text="SPIN"/>
</Pane>
controller
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class Controller {
@FXML
ReelPane reel;
@FXML
Button spin;
public void initialize() {
spin.setOnAction(event -> reel.spin());
}
}
main
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent root =
FXMLLoader.load(getClass().getResource("resources/fxml/slot.fxml"));
primaryStage.setScene(new Scene(root, 400, 900));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
But the children are not moving when button is clicked.Can someone tell what went wrong?
A much simpler tool for the needed animation is PauseTransition
.
If you do not want to specify the layout in the fxml you can override layoutChildren() as you did but you'll need to disable automatic layout (layoutChildren()) so the animation can change positions:
import java.util.List;
import javafx.animation.PauseTransition;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
public class ReelPane extends Pane {
private List<Node> managed;
private double dy, numberOfCchildren;
private boolean isAnimating = false;
private static final double PAUSE = 1;
@Override
protected void layoutChildren() {
if(isAnimating) return;
managed = getChildren();
numberOfCchildren = managed.size();
double y = 0;
for (Node node : managed) {
node.setLayoutX(0);
node.setLayoutY(y);
dy = Math.max(dy, node.getBoundsInLocal().getHeight()); //dy stores the highest
y += dy;
}
}
public void spin() {
isAnimating = true;
PauseTransition pause = new PauseTransition(Duration.seconds(PAUSE));
pause.setOnFinished(event ->{
for (Node node : managed) {
if(node.getLayoutY()>= (numberOfCchildren -1) * dy){
node.setLayoutY(-dy);
}
node.setLayoutY(node.getLayoutY() +dy);
}
pause.play();
});
pause.play();
}
}
Consider disabling the button while spinning:
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class Controller {
@FXML
ReelPane reel;
@FXML
Button spin;
public void initialize() {
spin.setOnAction(event -> {
reel.spin();
spin.setDisable(true);
});
}
}
To make the code mre here is the slot.fxml
using publicly available resources:
<?import javafx.scene.control.Button?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.Pane?>
<?import fx_tests.ReelPane?>
<Pane
xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx"
fx:controller="fx_tests.sample.Controller">
<ReelPane fx:id="reel">
<ImageView fitHeight="100" fitWidth="100">
<Image url="https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/128x128/Box_Green.png"/>
</ImageView>
<ImageView fitHeight="100" fitWidth="100" >
<Image url="https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/128x128/Box_Red.png"/>
</ImageView>
<ImageView fitHeight="100" fitWidth="100">
<Image url="https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/128x128/Box_Yellow.png"/>
</ImageView>
<ImageView fitHeight="100" fitWidth="100" >
<Image url="https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/128x128/Box_Blue.png"/>
</ImageView>
<ImageView fitHeight="100" fitWidth="100">
<Image url="https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/128x128/Box_Orange.png"/>
</ImageView>
</ReelPane>
<Button fx:id="spin" text="SPIN" layoutX="20." layoutY="550.0" />
</Pane>
A test application:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent root =
FXMLLoader.load(getClass().getResource("slot.fxml"));
primaryStage.setScene(new Scene(root, 200, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have already assigned the layout value in ReelPane layoutChildren method ``` protected void layoutChildren() { List<Node> managed = getChildren(); double y = 0; for (Node node : managed) { node.setLayoutY(y); node.setLayoutX(0); y += node.getBoundsInLocal().getHeight(); } }
Run the code I posted. It is working.
I'm trying to develop a custom reel pane inside which I can add images and buttons without specifying layoutx and layouty values. I have added the code for adding children vertically as in vbox inside the layoutchildren method. After that, I run the same animation code and it is not working
"without specifying layoutx and layouty values" I can't see this requirement in the post. Further more
Reel
is aPane
and not aVBox
so you need to set the position. The animation can be achieved withVBox
but it requires a different approach.ReelPane is a pane that adds children vertically using layoutchildren() method inside it. So I don't want to specify layoutx and layouty values explicitly.I have added the layoutchildren code inside reelpane.java