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添加拖拽触发事件,传递瓦片文本作为标识,同时生成拖拽预览图提升交互体验
- 拖放目标处理:
- 过滤合法拖拽源(非自身面板、携带瓦片数据)
- 计算目标列索引,判断位置是否为空,完成瓦片移动:移除源位置瓦片并替换为空白,将瓦片放入目标位置
- 跨面板兼容:通过获取拖拽源的父GridPane,统一处理同面板和跨面板的拖拽逻辑
内容的提问来源于stack exchange,提问作者chasse
相关产品推荐
相关产品推荐

