You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX GridPane多圆形移动难题:序列移动与速度控制求助

JavaFX GridPane多圆形序列移动实现方案

需求与问题

  • 拥有5个GridPane组件,每个包含10个垂直单元格
  • 需实现:指定数量的圆形从第0格开始逐格向下移动,到达最后一格后自动消失
  • 已完成单个圆形移动功能,当前面临两个核心难点:
    1. 多圆形的序列移动编排,保证队列顺序
    2. 单独调整每个圆形的移动速度,慢圆形需导致整体队列延迟

现有代码片段

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);
}

关键说明

  1. 队列延迟逻辑:每个圆形仅在前一个圆形从第0格移动到第1格后才启动,若前一个圆形速度慢,后续圆形的启动时间会自动延迟,整体队列节奏由最慢的圆形决定。
  2. 动画生命周期管理:圆形到达最后一格时,自动停止Timeline并移除自身,避免内存泄漏。
  3. 多GridPane适配:若要在5个GridPane上同时运行,只需为每个GridPane创建独立的CircleMovement列表并调用startNextMovement即可。

内容的提问来源于stack exchange,提问作者Jam2002

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 00:54:59