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

JavaFX GridPane洗牌后间距错乱问题求助

问题修复:洗牌后GridPane方块间距错乱

问题根源

  1. 动画属性残留:TranslateTransition执行后,方块的translateX/translateY属性未重置为0,GridPane布局时会叠加这些偏移值,导致间距错乱。
  2. 动画坐标计算错误:直接使用layoutX和绝对目标坐标计算动画偏移,而非基于位置差的相对偏移,导致动画结束后方块位置偏离布局预期。
  3. 字母生成逻辑漏洞:generateRandomLetter方法未将已生成的字母索引加入集合,会导致无限循环(无法生成不重复字母)。

修复方案

  1. 动画结束后重置translateX/translateY为0,消除动画对布局的影响。
  2. 重新计算动画偏移量:基于方块的**原始位置(初始列索引×方块大小)与目标位置(新列索引×方块大小)**的差值作为动画的偏移参数。
  3. 修复字母生成逻辑,将已使用的字母索引加入集合,确保生成唯一字母。

修改后的完整代码

package com.example.javafxdemo.shuffle;

import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.*;

public class ShuffleGridPaneExample_Fixed extends Application {

    private final int gridSize = 7; // 网格列数
    private final int squareSize = 80; // 每个方块的尺寸

    private final GridPane gridPane = new GridPane();
    private final List<Square> squares = new ArrayList<>();

    @Override
    public void start(Stage primaryStage) {
        createGridPane();

        Button shuffleButton = new Button("Shuffle");
        shuffleButton.setOnAction(event -> shuffleSquares());

        GridPane root = new GridPane();
        root.setAlignment(Pos.CENTER);
        root.add(gridPane, 0, 0);
        root.add(shuffleButton, 0, 1);

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Shuffle GridPane Example");
        primaryStage.show();
    }

    private void createGridPane() {
        Random random = new Random();
        for (int i = 0; i < gridSize; i++) {
            Square square = createSquare(generateRandomLetter(random), i);
            squares.add(square);
            gridPane.add(square, i, 0);
        }
    }

    static HashSet<Integer> generatedLetters = new LinkedHashSet<>();

    private char generateRandomLetter(Random random) {
        while (true) {
            int letterIndex = random.nextInt(26); // 生成0-25的随机索引
            if (!generatedLetters.contains(letterIndex)) {
                generatedLetters.add(letterIndex); // 添加已使用索引,避免重复
                return (char) ('A' + letterIndex); // 转换为对应字母
            }
        }
    }

    private Square createSquare(char letter, int columnIndex) {
        Square square = new Square(squareSize, squareSize, letter, columnIndex);
        GraphicsContext gc = square.getGraphicsContext2D();
        gc.setFill(generateRandomColor());
        gc.fillRect(0, 0, squareSize, squareSize);
        gc.setFill(Color.BLACK);
        gc.setFont(Font.font("Arial", FontWeight.BOLD, 40));
        gc.fillText(String.valueOf(letter), squareSize / 2 - 10, squareSize / 2 + 10);
        return square;
    }

    private void shuffleSquares() {
        // 记录每个方块当前的列索引
        for (Square square : squares) {
            square.setCurrentIndex(gridPane.getColumnIndex(square));
        }

        Collections.shuffle(squares);
        gridPane.getChildren().clear();

        int columnIndex = 0;
        for (Square square : squares) {
            gridPane.add(square, columnIndex, 0);
            square.setTargetIndex(columnIndex);
            columnIndex++;
        }

        animateTileMovement();
    }

    private void animateTileMovement() {
        ParallelTransition parallelTransition = new ParallelTransition();

        for (Square square : squares) {
            int startIndex = square.getCurrentIndex();
            int targetIndex = square.getTargetIndex();

            if (startIndex != targetIndex) {
                // 计算X方向相对偏移:从起始列到目标列的差值
                double startOffsetX = (startIndex - targetIndex) * squareSize;
                // Y方向飞行动画偏移
                double startOffsetY = -squareSize;

                TranslateTransition translateTransition = new TranslateTransition(Duration.seconds(3), square);
                translateTransition.setFromX(startOffsetX);
                translateTransition.setToX(0);
                translateTransition.setFromY(startOffsetY);
                translateTransition.setToY(0);

                // 动画结束后重置偏移属性,避免干扰后续布局
                translateTransition.setOnFinished(event -> {
                    square.setTranslateX(0);
                    square.setTranslateY(0);
                });

                parallelTransition.getChildren().add(translateTransition);
            }
        }

        parallelTransition.play();
    }

    public static void main(String[] args) {
        launch(args);
    }

    private static class Square extends Canvas {
        private final int originalIndex;
        private final char letter;
        private int currentIndex;
        private int targetIndex;

        public Square(double width, double height, char letter, int originalIndex) {
            super(width, height);
            this.originalIndex = originalIndex;
            this.letter = letter;
            this.currentIndex = originalIndex;
        }

        public int getOriginalIndex() {
            return originalIndex;
        }

        public int getCurrentIndex() {
            return currentIndex;
        }

        public void setCurrentIndex(int currentIndex) {
            this.currentIndex = currentIndex;
        }

        public int getTargetIndex() {
            return targetIndex;
        }

        public void setTargetIndex(int targetIndex) {
            this.targetIndex = targetIndex;
        }
    }

    private Color generateRandomColor() {
        Random random = new Random();
        double r = random.nextDouble();
        double g = random.nextDouble();
        double b = random.nextDouble();
        return new Color(r, g, b, 1.0);
    }
}

关键修改点说明

  • 新增Square类的索引属性:通过currentIndex和targetIndex记录洗牌前后的列位置,用于计算动画偏移。
  • 修正动画偏移逻辑:使用相对偏移量代替绝对坐标,确保动画结束后方块回到GridPane的正确布局位置。
  • 动画结束回调重置属性:彻底清除动画残留的translate值,避免影响后续洗牌操作的布局。
  • 修复字母生成逻辑:将已使用的字母索引加入集合,保证生成的字母唯一。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 12:29:57