JavaFX ListView禁用水平滚动

Dej*_*jwi 5 java listview javafx

我试图避免在ListView中水平滚动。ListView实例保存HBox项的列表,每个项具有不同的宽度。

到目前为止,我正在使用这样的单元工厂:

public class ListViewCell extends ListCell<Data>
{
    @Override
    public void updateItem(Data data, boolean empty)
    {
        super.updateItem(data, empty);
        if(empty || data == null){
            setGraphic(null);
            setText(null);
        }
        if(data != null)
        {
            Region region = createRow(data);
            region.prefWidthProperty().bind(mListView.widthProperty().subtract(20));
            region.maxWidthProperty().bind(mListView.widthProperty().subtract(20));
            setGraphic(region);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这还不够。通常在添加几个项目之后,ListView的水平滚动条就会出现。即使似乎没有必要。

如何确保ListViewCell不会超过其父级宽度,并且水平滚动条不会出现?

Ato*_*000 0

这里有很多因素使得自定义 ListView 水平滚动条行为变得难以处理。除此之外,对 ListView 工作原理的常见误解可能会导致其他问题。

要解决的主要问题是当垂直滚动条可见时,ListCells 的宽度不会自动调整。因此,就在此时,内容突然太宽,无法容纳在 ListView 的左边缘和垂直滚动条的左边缘之间,从而触发了水平滚动条。在确定要设置的正确绑定时,还需要考虑 ListCell 的默认填充以及 ListView 本身的边框宽度。

下面的类扩展了 ListView:

    public class WidthBoundList extends ListView {
        private final BooleanProperty vbarVisibleProperty = new SimpleBooleanProperty(false);
        private final boolean bindPrefWidth;
        private final double scrollbarThickness;
        private final double sumBorderSides;
        
        public WidthBoundList(double scrollbarThickness, double sumBorderSides, boolean bindPrefWidth) {
            this.scrollbarThickness = scrollbarThickness;
            this.sumBorderSides = sumBorderSides;
            this.bindPrefWidth = bindPrefWidth;
            Platform.runLater(()->{
                findScroller();
            });
        }
        
        private void findScroller() {
            if (!this.getChildren().isEmpty()) {
                VirtualFlow flow = (VirtualFlow)this.getChildren().get(0);
                if (flow != null) {
                    List<Node> flowChildren = flow.getChildrenUnmodifiable();
                    int len = flowChildren .size();
                    for (int i = 0; i < len; i++) {
                        Node n = flowChildren .get(i);
                        if (n.getClass().equals(VirtualScrollBar.class)) {
                            final ScrollBar bar = (ScrollBar) n;
                            if (bar.getOrientation().equals(Orientation.VERTICAL)) {
                                vbarVisibleProperty.bind(bar.visibleProperty());
                                bar.setPrefWidth(scrollbarThickness);
                                bar.setMinWidth(scrollbarThickness);
                                bar.setMaxWidth(scrollbarThickness);
                            } else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
                                bar.setPrefHeight(scrollbarThickness);
                                bar.setMinHeight(scrollbarThickness);
                                bar.setMaxHeight(scrollbarThickness);
                            }
                        }
                    }
                } else {
                    Platform.runLater(()->{
                        findScroller();
                    });
                }
            } else {
                Platform.runLater(()->{
                    findScroller();
                });
            }
        }
        
        public void bindWidthScrollCondition(Region node) {
            node.maxWidthProperty().unbind();
            node.prefWidthProperty().unbind();
            node.maxWidthProperty().bind(
                Bindings.when(vbarVisibleProperty)
                        .then(this.widthProperty().subtract(scrollbarThickness).subtract(sumBorderSides))
                        .otherwise(this.widthProperty().subtract(sumBorderSides))
            );
            if (bindPrefWidth) { 
                node.prefWidthProperty().bind(node.maxWidthProperty());
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

关于您的代码,您的绑定可能会导致问题。ListCell 的updateItem()方法不仅在创建 ListCell 时被调用。ListView 可以包含相当大的数据列表,因此为了提高性能,只有滚动到视图中的 ListCell(可能还有之前和之后的一些)需要渲染图形。该updateItem()方法处理这个问题。在您的代码中,会一遍又一遍地创建一个区域,并且每个区域都绑定到 ListView 的宽度。相反,ListCell 本身应该被绑定。

下面的类扩展了ListCell,并在构造函数中调用了绑定HBox的方法:

    public class BoundListCell extends ListCell<String> {
        private final HBox  hbox;
        private final Label label;
        
        public BoundListCell(WidthBoundList widthBoundList) {
            this.setPadding(Insets.EMPTY);
            hbox = new HBox();
            label = new Label();
            hbox.setPadding(new Insets(2, 4, 2, 4));
            hbox.getChildren().add(label);
            widthBoundList.bindWidthScrollCondition(this);
        }
        
        @Override
        public void updateItem(String data, boolean empty) {
            super.updateItem(data, empty);
            if (empty || data == null) {
                label.setText("");
                setGraphic(null);
                setText(null);
            } else {
                label.setText(data);
                setGraphic(hbox);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

WidthBoundList构造函数的参数scrollbarThickness已设置为12。该sumBorderSides参数已设置为2,因为我的WidthBoundList左右各有一个像素边框。该bindPrefWidth参数已设置为 true 以防止水平滚动条完全显示(标签有省略号,您可能添加到水平盒子的任何非文本节点都将被剪掉)。设置bindPrefWidth为 false 以允许水平滚动条,并且通过这些正确的绑定,它应该仅在需要时显示。一个实现:

    private final WidthBoundList myListView = new WidthBoundList(12, 2, true);
    
    public static void main(final String... a) {
        Application.launch(a);
    }
    
    @Override
    public void start(final Stage primaryStage) throws Exception {
        myListView.setCellFactory(c -> new BoundListCell(myListView));
        
        VBox vBox = new VBox();
        vBox.setFillWidth(true);
        vBox.setAlignment(Pos.CENTER);
        vBox.setSpacing(5);

        Button button = new Button("APPEND");
        button.setOnAction((e)->{
            myListView.getItems().add("THIS IS LIST ITEM NUMBER " + myListView.getItems().size());
        });
        
        vBox.getChildren().addAll(myListView, button);
        
        myListView.maxWidthProperty().bind(vBox.widthProperty().subtract(20));
        myListView.prefHeightProperty().bind(vBox.heightProperty().subtract(20));
        
        primaryStage.setScene(new Scene(vBox, 200, 400));
        primaryStage.show();
    }
Run Code Online (Sandbox Code Playgroud)