JavaFX ScrollPane - 检测滚动条何时可见?

JavaFX ScrollPane - Detect when scrollbar is visible?

我有一个 ScrollPane 如下:

    ScrollPane scroller = new ScrollPane();
    scroller.getStyleClass().add("scroller");
    scroller.setPrefWidth(width);
    scroller.setFocusTraversable(Boolean.FALSE);
    scroller.setPannable(Boolean.TRUE);
    scroller.setFitToWidth(Boolean.TRUE);
    scroller.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
    scroller.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
    this.setCenter(scroller);

    scroller.contentProperty().addListener((observableValue, last, now) ->
    {
        ScrollBar scrollBar = (ScrollBar) scroller.lookup(".scroll-bar:vertical");

        if (scrollBar != null)
        {
            if (scrollBar.isVisible())
            {
                log.info("Scrollbar visible, setting lower card width..");
            }
            else
            {
                log.info("Scrollbar not visible, setting default card width..");
            }
        }
    });

如您所见,我已将侦听器附加到内容 属性 以了解内容何时设置。我试图查看内容更新时滚动条是否可见。尽管我可以在 UI 上看到滚动条,但它总是转到其他部分 - “滚动条不可见”。

不确定是否有其他方法可以做到这一点?检查了很多 Whosebug 和 Oracle 文档 - 没有发现任何可靠的建议。

-- 为问题添加上下文以更好地理解:

只是想解释一下问题是什么,不确定是应该把它作为回复评论还是编辑问题,请指教并会更改它:

所以我有这个视图,它从 Firebase 中调出需要加载到 TilePane 上的记录,该 TilePane 托管在 ScrollPane 中,进入 BorderPane 的中心。

我从 Firebase 获得响应的时间无法预测,因为它是异步的。因此 UI 加载了空的 TilePane,然后异步调用去获取数据。当数据可用时,我需要准备卡片(即 HBox),但列数是固定的。所以不得不调整卡片的宽度,保持TilePane上的gap(16px)和padding(16px)一致,同时保持5列。每张卡片的宽度需要根据显示是否有滚动条重新计算。因为如果显示滚动条,它需要一些 space 并且 TilePane 会将它缩小到 4 列,留下很多空 space。如果不清楚,很乐意进一步解释。

我强烈建议遵循评论中给出的建议。关键在于选择正确的布局。

我回答这个问题的目的是,如果将来有人遇到这个关于处理滚动条可见性的问题,他们至少会知道一种方法(在 JavaFX 8).

检查滚动条可见性的一种方法是在 layoutChildren 上注册适当的滚动条并为其可见性添加一个侦听器 属性。像...

ScrollPane scrollPane = new ScrollPane() {
    ScrollBar vertical;

    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        if (vertical == null) {
            vertical = (ScrollBar) lookup(".scroll-bar:vertical");
            vertical.visibleProperty().addListener((obs, old, val) -> updateContent(val));
            updateContent(vertical.isVisible());
        }
    }
};

updateContent(visible) 方法是您在更新可见性时想要执行的操作。

一个完整的工作演示如下。

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ScrollPaneScrollBarVisibility_Demo extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        BorderPane borderPane = new BorderPane();
        Scene sc = new Scene(borderPane, 300, 300);
        stage.setScene(sc);
        stage.setTitle("ScrollBar visibility");
        stage.show();

        ScrollPane scrollPane = new ScrollPane() {
            ScrollBar vertical;

            @Override
            protected void layoutChildren() {
                super.layoutChildren();
                if (vertical == null) {
                    vertical = (ScrollBar) lookup(".scroll-bar:vertical");
                    vertical.visibleProperty().addListener((obs, old, val) -> updateContent(val));
                    updateContent(vertical.isVisible());
                }
            }
        };
        scrollPane.setContent(getContent());
        borderPane.setCenter(scrollPane);
    }

    private void updateContent(boolean scrollBarVisible) {
        System.out.println("Vertical scroll bar visible :: " + scrollBarVisible);
    }

    private VBox getContent() {
        VBox labels = new VBox();
        labels.setSpacing(5);
        for (int i = 0; i < 10; i++) {
            labels.getChildren().add(new Label("X " + i));
        }
        Button add = new Button("Add");
        add.setOnAction(e -> labels.getChildren().add(new Label("Text")));
        Button remove = new Button("Remove");
        remove.setOnAction(e -> {
            if (!labels.getChildren().isEmpty()) {
                labels.getChildren().remove(labels.getChildren().size() - 1);
            }
        });
        HBox buttons = new HBox(add, remove);
        buttons.setSpacing(15);

        VBox content = new VBox(buttons, labels);
        content.setPadding(new Insets(15));
        content.setSpacing(15);
        return content;
    }

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

正如@James_D所说,使用了 GridPane 并且它在没有任何监听器的情况下工作:

    GridPane cards = new GridPane();
    cards.setVgap(16);
    cards.setHgap(16);

    cards.setAlignment(Pos.CENTER);
    cards.setPadding(new Insets(16));

    ColumnConstraints constraints = new ColumnConstraints();
    constraints.setPercentWidth(20);
    constraints.setHgrow(Priority.ALWAYS);
    constraints.setFillWidth(Boolean.TRUE);

    cards.getColumnConstraints().addAll(constraints, constraints, constraints, constraints, constraints);

我有 5 列,所以有 5 次约束。工作得很好。