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

JavaFX网格渲染问题:如何让线条显示在矩形Tile上层?

JavaFX网格线条被相邻Tile矩形遮挡的解决方案

问题说明

我是JavaFX新手,制作GridTile16网格时遇到线条被相邻Tile矩形遮挡的问题。希望所有线条显示在所有矩形上层,尝试过.toFront()、.toBack()和viewOrderProperty(),但这些方法仅对当前Tile的子节点生效,无法解决跨Tile的层级问题。

相关代码

创建网格的代码

private static void makeGrid(Group root, int gridSize, ArrayList<String> grid, GridTile16[][] gridTiles, Scene scene) {
    for (int i = 0; i < grid.size(); i++) {
        for (int j = 0; j < grid.get(i).length(); j++) {
            GridTile16 newGridTile = new GridTile16(i * gridSize, j * gridSize, gridSize, grid.get(i).charAt(j));
            root.getChildren().add(newGridTile);
            gridTiles[i][j] = newGridTile;
        }
    }
}

GridTile基类代码

import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

public class GridTile extends Group {

    protected int x;
    protected int y;
    protected int size;
    protected Rectangle rectangle;

    public GridTile(int x, int y, int size) {
        this.x = x;
        this.y = y;
        this.size = size;
        Rectangle rectangle = new Rectangle(x, y, size, size);
        rectangle.setFill(Color.BLACK);
        rectangle.setStroke(Color.WHITE);
        this.getChildren().add(rectangle);
        this.rectangle = rectangle;
    }

    public void resetColor() {
        this.rectangle.setFill(Color.BLACK);
    }

    public int getX() {
        return y/size;
    }

    public int getY() {
        return x/size;
    }
}

GridTile16子类代码

import com.programs.GridTile;

import javafx.scene.paint.Color;
import javafx.scene.shape.Line;

public class GridTile16 extends GridTile {

    private boolean[] isFromLeftRightTopBot = new boolean[4];

    public GridTile16(int y, int x, int size, char c) {
        super(x, y , size);
        if (c != '.') {
            Line line = null;
            switch (c) {
                case '-':
                    line = new Line(x, y + size / 2, x + size, y + size / 2);
                    break;
                case '|':
                    line = new Line(x + size / 2, y, x + size / 2, y + size);
                    break;
                case '/':
                    line = new Line(x, y + size, x + size, y);
                    break;
                case '\\':
                    line = new Line(x, y, x + size, y + size);
                    break;
            }
            line.setStroke(Color.WHITE);
            line.setStrokeWidth(5);
            this.getChildren().add(line);
        }
    }

    public boolean getIsFromDirection(int direction) {
        return this.isFromLeftRightTopBot[direction];
    }

    public void setIsFromDirection(int direction) {
        this.isFromLeftRightTopBot[direction] = true;
        this.rectangle.setFill(Color.BLUE);
        Line newLine = null;
        if (direction == 0) {
            newLine = new Line(this.x, this.y + this.size / 2, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 1) {
            newLine = new Line(this.x + this.size, this.y + this.size / 2, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 2) {
            newLine = new Line(this.x + this.size / 2, this.y, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 3) {
            newLine = new Line(this.x + this.size / 2, this.y + this.size, this.x + this.size / 2, this.y + this.size / 2);
        }
        newLine.setStrokeWidth(2);
        newLine.setStroke(Color.RED);
        this.getChildren().add(newLine);
    }
}

主程序代码

public class App extends Application {

    private static final int gridSize = 40;
    private static double zoomFactor = 1;

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

    @Override
    public void start(Stage stage) throws FileNotFoundException {

        Group root = new Group();
        Scene scene = new Scene(root, 400, 400, Color.BLACK);
        stage.setFullScreen(true);
        stage.setFullScreenExitHint("");
        stage.setTitle("Visualisation");
        stage.setScene(scene);
        stage.show();

        scene.widthProperty().addListener((obs, oldVal, newVal) -> {
            resetSize(scene, root);
        });
       
        scene.heightProperty().addListener((obs, oldVal, newVal) -> {
            resetSize(scene, root);
        });

        File file = new File(<INPUT>);
        Scanner scanner = new Scanner(file);
        ArrayList<String> grid = new ArrayList<>();
        while (scanner.hasNextLine()) {
            grid.add(scanner.nextLine());
        }
        scanner.close();
        GridTile16[][] gridTiles = new GridTile16[grid.size()][grid.get(0).length()];
        makeGrid(root, gridSize, grid, gridTiles, scene);

        resetSize(scene, root);
    }

    private static void resetSize(Scene scene, Group root) {
        root.setLayoutX((scene.getWidth() - root.prefWidth(-1)) / 2);
        root.setLayoutY((scene.getHeight() - root.prefHeight(-1)) / 2);

        double scaleX = scene.getWidth() / root.getBoundsInLocal().getWidth();
        double scaleY = scene.getHeight() / root.getBoundsInLocal().getHeight();
        zoomFactor = Math.min(scaleX, scaleY) - 0.01;
        updateTransforms(root);
    }

    private static void updateTransforms(Group root) {
        root.setScaleX(zoomFactor);
        root.setScaleY(zoomFactor);
    }
}

程序输入

.|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....

解决方案

核心思路是分离矩形层和线条层,让所有线条统一放在顶层容器中,从根本上避免被后续添加的Tile矩形遮挡。

具体实现步骤

  1. 在主程序中拆分root容器,新增两个独立Group分别存放矩形和线条:
// 替换原有root初始化代码
Group rectangleLayer = new Group();
Group lineLayer = new Group();
Group root = new Group(rectangleLayer, lineLayer);

JavaFX容器内子节点按添加顺序渲染,后添加的lineLayer会自动在rectangleLayer上方显示。

  1. 修改GridTile16类,添加线条层参数并调整线条添加逻辑:
public class GridTile16 extends GridTile {
    private boolean[] isFromLeftRightTopBot = new boolean[4];
    private Group lineLayer; // 新增线条层成员变量

    // 修改构造函数,传入lineLayer
    public GridTile16(int y, int x, int size, char c, Group lineLayer) {
        super(x, y , size);
        this.lineLayer = lineLayer;
        if (c != '.') {
            Line line = null;
            switch (c) {
                case '-':
                    line = new Line(x, y + size / 2, x + size, y + size / 2);
                    break;
                case '|':
                    line = new Line(x + size / 2, y, x + size / 2, y + size);
                    break;
                case '/':
                    line = new Line(x, y + size, x + size, y);
                    break;
                case '\\':
                    line = new Line(x, y, x + size, y + size);
                    break;
            }
            line.setStroke(Color.WHITE);
            line.setStrokeWidth(5);
            lineLayer.getChildren().add(line); // 将线条添加到顶层线条层
        }
    }

    // 修改setIsFromDirection方法,同样将红色线条加入顶层线条层
    public void setIsFromDirection(int direction) {
        this.isFromLeftRightTopBot[direction] = true;
        this.rectangle.setFill(Color.BLUE);
        Line newLine = null;
        if (direction == 0) {
            newLine = new Line(this.x, this.y + this.size / 2, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 1) {
            newLine = new Line(this.x + this.size, this.y + this.size / 2, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 2) {
            newLine = new Line(this.x + this.size / 2, this.y, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 3) {
            newLine = new Line(this.x + this.size / 2, this.y + this.size, this.x + this.size / 2, this.y + this.size / 2);
        }
        newLine.setStrokeWidth(2);
        newLine.setStroke(Color.RED);
        lineLayer.getChildren().add(newLine); // 调整线条添加目标
    }

    // 保留原有getIsFromDirection方法
    public boolean getIsFromDirection(int direction) {
        return this.isFromLeftRightTopBot[direction];
    }
}
  1. 更新makeGrid方法,传入拆分后的两个容器:
private static void makeGrid(Group rectangleLayer, Group lineLayer, int gridSize, ArrayList<String> grid, GridTile16[][] gridTiles, Scene scene) {
    for (int i = 0; i < grid.size(); i++) {
        for (int j = 0; j < grid.get(i).length(); j++) {
            GridTile16 newGridTile = new GridTile16(i * gridSize, j * gridSize, gridSize, grid.get(i).charAt(j), lineLayer);
            rectangleLayer.getChildren().add(newGridTile);
            gridTiles[i][j] = newGridTile;
        }
    }
}
  1. 在主程序中调用makeGrid时传入对应容器:
makeGrid(rectangleLayer, lineLayer, gridSize, grid, gridTiles, scene);

方案优势

通过分层管理,一次性解决所有线条的层级问题,无需逐个调整节点顺序,代码逻辑更清晰,维护成本更低,完全符合JavaFX的渲染机制。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 00:12:32