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

JavaFX实现两个GridPane间及内部的瓦片拖拽功能需求

GridPane内部及跨GridPane的瓦片拖拽实现

以下是基于JavaFX的完整实现代码,满足你1行10列GridPane、初始各5个瓦片,支持内部和跨面板拖拽到空白位置的需求:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class TileDragApp extends Application {
    private static final int GRID_ROWS = 1;
    private static final int GRID_COLS = 10;
    private static final int TILE_SIZE = 50;

    @Override
    public void start(Stage primaryStage) {
        GridPane rack = createGridPane();
        GridPane board = createGridPane();

        // 初始化瓦片:每个GridPane前5个位置放瓦片
        initTiles(rack);
        initTiles(board);

        // 布局两个GridPane
        StackPane root = new StackPane();
        root.setPadding(new Insets(20));
        root.setSpacing(20);
        root.getChildren().addAll(rack, board);

        Scene scene = new Scene(root, 1100, 100);
        primaryStage.setTitle("Tile Drag Demo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private GridPane createGridPane() {
        GridPane grid = new GridPane();
        grid.setHgap(5);
        grid.setVgap(5);
        grid.setPadding(new Insets(10));

        // 先填满空白占位,方便后续判断位置是否为空
        for (int col = 0; col < GRID_COLS; col++) {
            StackPane emptySlot = new StackPane();
            emptySlot.setMinSize(TILE_SIZE, TILE_SIZE);
            emptySlot.setStyle("-fx-border-color: gray; -fx-border-width: 1px;");
            grid.add(emptySlot, col, 0);
        }

        // 给GridPane添加拖放目标事件
        setupDropTarget(grid);

        return grid;
    }

    private void initTiles(GridPane grid) {
        for (int col = 0; col < 5; col++) {
            Tile tile = new Tile("Tile " + (col + 1));
            // 替换对应位置的空白占位
            grid.getChildren().set(col, tile);
            // 给瓦片添加拖拽事件
            setupDragSource(tile);
        }
    }

    private void setupDragSource(Tile tile) {
        tile.setOnDragDetected(event -> {
            Dragboard db = tile.startDragAndDrop(TransferMode.MOVE);
            ClipboardContent content = new ClipboardContent();
            // 传递瓦片本身作为数据标识
            content.putString(tile.getText());
            db.setContent(content);
            // 生成拖拽预览图
            db.setDragView(tile.snapshot(null, null));
            event.consume();
        });

        tile.setOnDragDone(DragEvent::consume);
    }

    private void setupDropTarget(GridPane grid) {
        grid.setOnDragOver(event -> {
            if (event.getGestureSource() != grid && event.getDragboard().hasString()) {
                event.acceptTransferModes(TransferMode.MOVE);
            }
            event.consume();
        });

        grid.setOnDragDropped(event -> {
            Dragboard db = event.getDragboard();
            boolean success = false;

            if (db.hasString()) {
                // 计算目标位置的列索引
                int targetCol = (int) Math.floor((event.getX() - grid.getPadding().getLeft()) / (TILE_SIZE + grid.getHgap()));
                if (targetCol < 0 || targetCol >= GRID_COLS) {
                    event.setDropCompleted(false);
                    event.consume();
                    return;
                }

                // 判断目标位置是否为空白占位
                StackPane targetSlot = (StackPane) grid.getChildren().get(targetCol);
                if (targetSlot.getChildren().isEmpty()) {
                    // 找到拖拽源的瓦片
                    Tile sourceTile = null;
                    for (javafx.scene.Node node : ((GridPane) event.getGestureSource()).getChildren()) {
                        if (node instanceof Tile && ((Tile) node).getText().equals(db.getString())) {
                            sourceTile = (Tile) node;
                            break;
                        }
                    }

                    if (sourceTile != null) {
                        // 源位置替换为空白占位
                        GridPane sourceGrid = (GridPane) sourceTile.getParent().getParent();
                        int sourceCol = GridPane.getColumnIndex(sourceTile);
                        StackPane emptySlot = new StackPane();
                        emptySlot.setMinSize(TILE_SIZE, TILE_SIZE);
                        emptySlot.setStyle("-fx-border-color: gray; -fx-border-width: 1px;");
                        sourceGrid.getChildren().set(sourceCol, emptySlot);

                        // 将瓦片放入目标位置
                        grid.getChildren().set(targetCol, sourceTile);
                        GridPane.setColumnIndex(sourceTile, targetCol);
                        success = true;
                    }
                }
            }

            event.setDropCompleted(success);
            event.consume();
        });
    }

    // 自定义瓦片组件
    static class Tile extends StackPane {
        private final Label label;

        public Tile(String text) {
            Rectangle bg = new Rectangle(TILE_SIZE, TILE_SIZE, Color.LIGHTBLUE);
            bg.setStroke(Color.DARKBLUE);
            label = new Label(text);
            getChildren().addAll(bg, label);
            setStyle("-fx-cursor: hand;");
        }

        public String getText() {
            return label.getText();
        }
    }

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

关键逻辑说明

  • 空白占位初始化:每个GridPane先填满空白StackPane,直接通过索引判断目标位置是否为空,简化布局坐标计算
  • 拖拽源配置:给每个Tile添加拖拽触发事件,传递瓦片文本作为标识,同时生成拖拽预览图提升交互体验
  • 拖放目标处理:
    1. 过滤合法拖拽源(非自身面板、携带瓦片数据)
    2. 计算目标列索引,判断位置是否为空,完成瓦片移动:移除源位置瓦片并替换为空白,将瓦片放入目标位置
  • 跨面板兼容:通过获取拖拽源的父GridPane,统一处理同面板和跨面板的拖拽逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 04:07:47