JavaFX GridPane多圆形移动难题:序列移动与速度控制求助
JavaFX GridPane多圆形序列移动实现方案
需求与问题
- 拥有5个GridPane组件,每个包含10个垂直单元格
- 需实现:指定数量的圆形从第0格开始逐格向下移动,到达最后一格后自动消失
- 已完成单个圆形移动功能,当前面临两个核心难点:
- 多圆形的序列移动编排,保证队列顺序
- 单独调整每个圆形的移动速度,慢圆形需导致整体队列延迟
现有代码片段
package phase1; import javafx.animation.*; import Classes.Person; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Group; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; import javafx.stage.Stage; import javafx.util.Duration; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class Phase1 extends Application { ArrayList<Circle> PEOPLE; private List<Circle> objects; private Timer timer; private boolean isRowEmpty(GridPane gridPane, int rowIndex) { for (javafx.scene.Node node : gridPane.getChildren()) { Integer nodeRowIndex = GridPane.getRowIndex(node); if (nodeRowIndex != null && nodeRowIndex.intValue() == rowIndex) { return false; } } return true; } @Override public void start(Stage stage) throws InterruptedException, IOException { // Create the root container (AnchorPane) Parent fxmlRoot = new FXMLLoader().load(getClass().getResource("/PHASE.fxml")); // Create the controller and move the circles Phase1Controller controller = new Phase1Controller(); Group root = new Group(); //Creating a Group root.getChildren().add(fxmlRoot); GridPane graid1 = (GridPane) fxmlRoot.lookup("#Grid2"); objects = new ArrayList<>(); //PEOPLE = Phase1Controller.show_circles(fxmlRoot, 10); ArrayList<Person> people = Main1.generatePeople(5); Circle c1 = people.get(0).circle; Circle c2 = new Circle(22.5,Color.RED); timer = new Timer(); graid1.add(c1,0,0); int RowIndex = 0; AtomicInteger rowIndex = new AtomicInteger(0); AtomicBoolean ss = new AtomicBoolean(true); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { Platform.runLater(() -> { int currentRowIndex = rowIndex.getAndIncrement(); if (currentRowIndex >0){ //graid1.getChildren().set(currentRowIndex-1,c2); } // Add the node to the new row graid1.getChildren().remove(c1); // Add the node to the new row graid1.add(c1, 0, currentRowIndex); int pp = graid1.getRowConstraints().size(); System.out.println(pp); if(pp == currentRowIndex){ graid1.getChildren().remove(c1); timer.cancel();} }); } }, 0, 1000);// Run every 1 second // Create a scene and set it to the stage Scene scene = new Scene(root); stage.setTitle("Team 1"); stage.setResizable(true); // Display the Resizable stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(); } }
单个圆形运动效果

解决方案
核心改进思路
放弃Timer工具,改用JavaFX原生的Timeline动画API——它专为UI线程设计,无需手动处理线程切换,避免并发问题,同时更易于控制动画生命周期。
步骤1:封装圆形移动状态
创建CircleMovement类,统一管理每个圆形的移动状态、速度、所属GridPane及动画逻辑:
class CircleMovement { private Circle circle; private GridPane gridPane; private int currentRow; private long moveDelay; // 每格移动的间隔毫秒数 private boolean completed; private Timeline timeline; public CircleMovement(Circle circle, GridPane gridPane, long moveDelay) { this.circle = circle; this.gridPane = gridPane; this.currentRow = 0; this.moveDelay = moveDelay; this.completed = false; // 初始添加到第0格 this.gridPane.add(circle, 0, currentRow); } /** * 启动圆形移动 * @param onMoveToNextRow 每移动一格后的回调(用于触发下一个圆形启动) * @param onComplete 移动完成后的回调 */ public void startMovement(Runnable onMoveToNextRow, Runnable onComplete) { timeline = new Timeline(new KeyFrame(Duration.millis(moveDelay), event -> { currentRow++; gridPane.getChildren().remove(circle); // 到达最后一格,结束移动 if (currentRow >= gridPane.getRowConstraints().size()) { completed = true; timeline.stop(); if (onComplete != null) { onComplete.run(); } return; } // 移动到下一格 gridPane.add(circle, 0, currentRow); // 触发移动回调 if (onMoveToNextRow != null) { onMoveToNextRow.run(); } })); // 设置无限循环,直到到达最后一格 timeline.setCycleCount(Timeline.INDEFINITE); timeline.play(); } public boolean isCompleted() { return completed; } public long getMoveDelay() { return moveDelay; } }
步骤2:实现队列编排逻辑
通过递归方式启动圆形队列,保证每个圆形在前一个圆形移动一格后才开始移动,自然实现慢圆形拖慢整体队列的效果:
@Override public void start(Stage stage) throws InterruptedException, IOException { Parent fxmlRoot = new FXMLLoader().load(getClass().getResource("/PHASE.fxml")); Group root = new Group(); root.getChildren().add(fxmlRoot); GridPane grid1 = (GridPane) fxmlRoot.lookup("#Grid2"); ArrayList<Person> people = Main1.generatePeople(5); List<CircleMovement> movements = new ArrayList<>(); // 为每个圆形设置不同移动速度(单位:毫秒/格),模拟快慢差异 long[] moveDelays = {1000, 2000, 1000, 1500, 1000}; for (int i = 0; i < people.size(); i++) { movements.add(new CircleMovement(people.get(i).circle, grid1, moveDelays[i])); } // 启动队列移动 startNextMovement(movements, 0); Scene scene = new Scene(root); stage.setTitle("Team 1"); stage.setResizable(true); stage.setScene(scene); stage.show(); } /** * 递归启动下一个圆形的移动 */ private void startNextMovement(List<CircleMovement> movements, int index) { if (index >= movements.size()) { return; } CircleMovement currentMovement = movements.get(index); // 标记是否已启动下一个圆形,避免重复触发 boolean[] nextStarted = {false}; currentMovement.startMovement(() -> { // 当前圆形第一次移动后,启动下一个圆形 if (!nextStarted[0]) { nextStarted[0] = true; startNextMovement(movements, index + 1); } }, null); }
关键说明
- 队列延迟逻辑:每个圆形仅在前一个圆形从第0格移动到第1格后才启动,若前一个圆形速度慢,后续圆形的启动时间会自动延迟,整体队列节奏由最慢的圆形决定。
- 动画生命周期管理:圆形到达最后一格时,自动停止
Timeline并移除自身,避免内存泄漏。 - 多GridPane适配:若要在5个GridPane上同时运行,只需为每个GridPane创建独立的
CircleMovement列表并调用startNextMovement即可。
内容的提问来源于stack exchange,提问作者Jam2002
相关产品推荐
相关产品推荐

