响应式 UI-在 ScrollPane 中使用 TilePane 进行设计

Responsive UI-Design with a TilePane in a ScrollPane

我正在努力研究 Scroll- 和 Tilepanes atm,但我遇到了一个问题,如果不进行肮脏的黑客攻击,我将无法解决。

我有一个水平的 TilePane,它有 8 个图块,我将它设置为有 4 列,结果是 2 行有 4 个图块。 我把 TilePane 放在 HBox 中,因为如果我把它放在 StackPane 中,它会拉伸 tilepane 的大小,使我的 colum 设置无效。有点奇怪,设置 prefColumns/Rows 会重新计算 TilePane 的大小,而不是尝试设置 columns/rows 的实际数量,感觉更像是一个肮脏的 hack。

无论如何,将HBox直接放入ScrollPane中也不行,因为即使在第2行tile被切断后Scrollbars也不会出现。在 Stackpane 中再次设置该 HBox,然后我将其放入 ScrollPane 中就可以了。至少在我将 window 的宽度调整到如此小之前,tilepane 必须重新对齐瓷砖并出现第 3 行或更多行。

基本程序如下:

public class Main extends Application {

    @Override
    public void start(Stage stage) {

        TilePane tilePane = new TilePane();
        tilePane.setPadding(new Insets(5));
        tilePane.setVgap(4);
        tilePane.setHgap(4);
        tilePane.setPrefColumns(4);
        tilePane.setStyle("-fx-background-color: lightblue;");

        HBox tiles[] = new HBox[8];
        for (int i = 0; i < 8; i++) {
            tiles[i] = new HBox(new Label("This is node #" + i));
            tiles[i].setStyle("-fx-border-color: black;");
            tiles[i].setPadding(new Insets(50));
            tilePane.getChildren().add(tiles[i]);
        }

        HBox hbox = new HBox();
        hbox.setAlignment(Pos.CENTER);
        hbox.setStyle("-fx-background-color: blue;");
        hbox.getChildren().add(tilePane); 

        StackPane stack = new StackPane();
        stack.getChildren().add(hbox);

        ScrollPane sp = new ScrollPane();
        sp.setFitToHeight(true);
        sp.setFitToWidth(true);
        sp.setContent(stack);

        stage.setScene(new Scene(sp, 800, 600));
        stage.show();
    }

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

我设法实现了我想要的行为,但它更像是一个非常肮脏的 hack。我向包含 TilePane 的 HBox 的高度和宽度添加了一个侦听器,并假设当高度发生变化时,因为宽度变得太小以至于删除了一列并添加了一个新行。为了能够做到这一点,我将 HBox 放在 VBox 中,这样它就不会随着 ScrollPane 的高度而增长。对于宽度我简单计算了一下如果有space显示另一个colum(最多4个),就去做。

更改如下:

public class Main extends Application {

    private boolean notFirstPassHeight;
    private boolean notFirstPassWidth;

    @Override
    public void start(Stage stage) {

        TilePane tilePane = new TilePane();
        tilePane.setPadding(new Insets(5));
        tilePane.setVgap(4);
        tilePane.setHgap(4);
        tilePane.setPrefColumns(4);
        tilePane.setStyle("-fx-background-color: lightblue;");
        // I took the value from ScenicView
        tilePane.prefTileWidthProperty().set(182);

        HBox tiles[] = new HBox[8];
        for (int i = 0; i < 8; i++) {
            tiles[i] = new HBox(new Label("This is node #" + i));
            tiles[i].setStyle("-fx-border-color: black;");
            tiles[i].setPadding(new Insets(50));
            tilePane.getChildren().add(tiles[i]);
        }

        ScrollPane sp = new ScrollPane();
        sp.setFitToHeight(true);
        sp.setFitToWidth(true);

        StackPane stack = new StackPane();

        VBox vbox = new VBox();
        vbox.setStyle("-fx-background-color: red");

        HBox hbox = new HBox();
        hbox.setAlignment(Pos.CENTER);
        hbox.setStyle("-fx-background-color: blue;");
        hbox.getChildren().add(tilePane);

        notFirstPassHeight = false;
        notFirstPassWidth = false;
        hbox.heightProperty().addListener((observable, oldValue, newValue) -> {
            if (oldValue.doubleValue() < newValue.doubleValue() && notFirstPassHeight) {
                tilePane.setPrefColumns(tilePane.getPrefColumns() - 1);
                stack.requestLayout();
            }
            notFirstPassHeight = true;
        });
        hbox.widthProperty().addListener((observable, oldValue, newValue) -> {
             if (oldValue.doubleValue() < newValue.doubleValue() && notFirstPassWidth && tilePane.getPrefColumns() <= 3
                     && (newValue.doubleValue() / (tilePane.getPrefColumns() + 1)) > tilePane.getPrefTileWidth()) {
                 tilePane.setPrefColumns(tilePane.getPrefColumns() + 1);
                 stack.requestLayout();
             }
             notFirstPassWidth = true;
        });

        vbox.getChildren().add(hbox);

        stack.getChildren().add(vbox);

        sp.setContent(stack);
        stage.setScene(new Scene(sp, 800, 600));
        stage.show();
    }

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

}

但是这种方法需要我

1.Know the Width of the Tiles in the Tilepane.

2.Consider Padding and Gap between tiles for my calculation to be accurate, which I dont do in my example.

如果你问我,这无论如何都不是一个好方法。对于这样一个基本的东西来说,过程太复杂了。必须有一种更好、更简单的方法来实现完全的可调整大小和 ScrollPane 中的 TilePanes 所需的行为。

TilePane 中设置 and/or 行的首选列数决定了该磁贴窗格的 prefWidthprefHeight 值的计算。如果你想强制最大列数,你只需要使 maxWidth 等于计算的 prefWidth:你可以用

tilePane.setMaxWidth(Region.USE_PREF_SIZE);

这意味着(只要 tile pane 被放置在管理布局的东西中),它永远不会比 pref 宽度更宽,这是计算以允许首选的列数。当然,它可能比那个小。 (请注意,如果您需要最小列数而不是最大列数,则可以对 setMinWidth 使用相同的技巧。)

滚动窗格的 fitToHeightfitToWidth 属性将在为真时尝试将内容的高度(分别为宽度)调整为等于滚动窗格的高度(宽度)视口。这些操作将优先于内容的首选高度(宽度),但会尝试遵守最小高度(宽度)。

因此,同时调用 setFitToWidth(true)setFitToHeight(true) 通常是错误的,因为这几乎总是会完全关闭滚动(只是强制内容与滚动窗格的视口大小相同).

所以在这里你想要让 tile pane 的最大宽度符合 pref 宽度,并将 tile pane 的宽度固定为滚动窗格视口的宽度(这样当你缩小window,它缩小了视口的宽度并创建了更多的列)。如果行数增长到足够大,这将添加一个垂直滚动条,并且仅当视口水平收缩到低于平铺窗格的最小宽度(计算为所有节点的首选宽度的最小值)时才添加水平滚动条包含)。

我认为您的原始代码的以下版本基本上可以满足您的需求:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;

public class ScrollingTilePane extends Application {

    @Override
    public void start(Stage stage) {

        TilePane tilePane = new TilePane();
        tilePane.setPadding(new Insets(5));
        tilePane.setVgap(4);
        tilePane.setHgap(4);
        tilePane.setPrefColumns(4);
        tilePane.setStyle("-fx-background-color: lightblue;");

        // dont grow more than the preferred number of columns:
        tilePane.setMaxWidth(Region.USE_PREF_SIZE);

        HBox tiles[] = new HBox[8];
        for (int i = 0; i < 8; i++) {
            tiles[i] = new HBox(new Label("This is node #" + i));
            tiles[i].setStyle("-fx-border-color: black;");
            tiles[i].setPadding(new Insets(50));
            tilePane.getChildren().add(tiles[i]);
        }

        HBox hbox = new HBox();
        hbox.setAlignment(Pos.CENTER);
        hbox.setStyle("-fx-background-color: blue;");
        hbox.getChildren().add(tilePane); 

//        StackPane stack = new StackPane();
//        stack.getChildren().add(tilePane);
//        stack.setStyle("-fx-background-color: blue;");

        ScrollPane sp = new ScrollPane();
//        sp.setFitToHeight(true);
        sp.setFitToWidth(true);
        sp.setContent(hbox);

        stage.setScene(new Scene(sp, 800, 600));
        stage.show();
    }

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

请注意,如果您需要在滚动窗格内容之外更改 space 的背景颜色,您可以在外部样式中使用以下内容 sheet:

.scroll-pane .viewport {
  -fx-background-color: red ;
}