具有不同单元格高度的列表视图

时间:2019-08-21 13:05:35

标签: java listview javafx

我正在尝试做一个ListView,其中所选单元格具有不同的UI(分别具有不同的高度)。这些单元格在FXML中声明,并创建了自定义控件(DisplayRowDefaultDisplayRowSelected)来加载相应的FXML文件。

我还设置了一个单元工厂,在该工厂中,我根据单元是否被选中来管理单元的渲染。

listView.setCellFactory(lv -> new ListCell<>() {
        private DisplayRowSelected selectedGraphics;
        private DisplayRowDefault defaultGraphics;

        {
            defaultGraphics = new DisplayRowDefault();
            selectedGraphics = new DisplayRowSelected();
        }

        @Override
        protected void updateItem(Item item, boolean empty) {
            super.updateItem(item, empty);

            if(empty || item == null) {
                setContentDisplay(ContentDisplay.TEXT_ONLY);
                setGraphic(null);
            }
            else {
                selectedGraphics.setIndex(getListView().getItems().indexOf(item));
                selectedGraphics.setItem(item);

                setGraphic(isSelected() ? selectedGraphics : defaultGraphics);
                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            }
        }
    }
);

一切都“完美”地工作,除了像元大小保持不变。

编辑:我找到了解决该问题的方法。覆盖computePrefHeight时,将正确调整单元格的大小。这种方法的缺点是必须明确指定prefHeight。

listView.setCellFactory(lv -> new ListCell<>() {
        private DisplayRowSelected selectedGraphics;
        private DisplayRowDefault defaultGraphics;

        {
            defaultGraphics = new DisplayRowDefault();
            defaultGraphics.setPrefHeight(50);

            selectedGraphics = new DisplayRowSelected();
            selectedGraphics.setPrefHeight(100);
        }

        @Override
        protected void updateItem(Item item, boolean empty) {
            super.updateItem(item, empty);

            if(empty || item == null) {
                setContentDisplay(ContentDisplay.TEXT_ONLY);
                setGraphic(null);
            }
            else {
                selectedGraphics.setIndex(getListView().getItems().indexOf(item));
                selectedGraphics.setItem(item);

                setGraphic(isSelected() ? selectedGraphics : defaultGraphics);
                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            }

            setPrefHeight(Region.USE_COMPUTED_SIZE);
        }

        @Override
        protected double computePrefHeight(double v) {
            if(getContentDisplay() == ContentDisplay.GRAPHIC_ONLY) {
                return isSelected() ? selectedGraphics.getPrefHeight() : defaultGraphics.getPrefHeight();
            }

            return super.computePrefHeight(v);
        }
    }
);

修改:MCVE

public class Sample extends Application {
    private class SelectedCell extends VBox {
        public SelectedCell() {
            getChildren().add(new Label("---"));
            getChildren().add(new Label("Selected Cell"));
            getChildren().add(new Label("---"));
        }
    }

    private class DefaultCell extends VBox {
        public DefaultCell() {
            getChildren().add(new Label("Default Cell"));
        }
    }

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

    @Override
    public void start(Stage primaryStage) {
        List<String> items = List.of("Item A", "Item B", "Item C", "Item D");

        ListView<String> listView = new ListView<>();
        listView.getItems().setAll(items);

        listView.setCellFactory(lv -> new ListCell<>() {
            private SelectedCell selectedCell = new SelectedCell();
            private DefaultCell defaultCell = new DefaultCell();

            @Override
            protected void updateItem(String s, boolean b) {
                super.updateItem(s, b);

                if(s == null || b) {
                    setContentDisplay(ContentDisplay.TEXT_ONLY);
                    setGraphic(null);
                }
                else {
                    setGraphic(isSelected() ? selectedCell : defaultCell);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                }
            }
        });

        Scene scene = new Scene(listView, 200, 500);

        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

2 个答案:

答案 0 :(得分:2)

尽管通过显式询问prefHeight(我在问题中提到过)的解决方案是可行的,但这并不是一个好的解决方案。原因是各个Node的尺寸取决于放置它们的上下文。因此,不能总是确定prefHeight值。

通过设计,ListView的设计使得在编辑模式下,单元格使用不同的UI,并且更改模式会导致正确地重新计算单元格大小。因此,我选择了一种在更改选择内容时开始/停止编辑当前单元格的方法。

这是我找到的解决方案,并且可以正常工作:

public class Sample extends Application {
    private class SelectedCell extends VBox {
        public SelectedCell() {
            getChildren().add(new Label("---"));
            getChildren().add(new Label("Selected Cell"));
            getChildren().add(new Label("---"));
        }
    }

    private class DefaultCell extends VBox {
        public DefaultCell() {
            getChildren().add(new Label("Default Cell"));
        }
    }

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

    @Override
    public void start(Stage primaryStage) {
        List<String> items = List.of("Item A", "Item B", "Item C", "Item D");

        ListView<String> listView = new ListView<>();
        listView.getItems().setAll(items);

        listView.setEditable(true);

        listView.setCellFactory(lv -> new ListCell<>() {
            private SelectedCell selectedCell = new SelectedCell();
            private DefaultCell defaultCell = new DefaultCell();

            {
                selectedProperty().addListener((observable, oldValue, newValue) -> {
                    if (oldValue != null && oldValue) {
                        cancelEdit();
                    }

                    if (newValue != null && newValue) {
                        startEdit();
                    }
                });
            }

            @Override
            public void startEdit() {
                super.startEdit();
                setGraphic(selectedCell);
            }

            @Override
            public void cancelEdit() {
                if(!isSelected()) {
                    super.cancelEdit();
                    setGraphic(defaultCell);
                }
            }

            @Override
            protected void updateItem(String s, boolean b) {
                super.updateItem(s, b);

                if(s == null || b) {
                    setContentDisplay(ContentDisplay.TEXT_ONLY);
                    setGraphic(null);
                }
                else {
                    setGraphic(isEditing() ? selectedCell : defaultCell);
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                }
            }
        });

        Scene scene = new Scene(listView, 200, 500);

        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

答案 1 :(得分:2)

如评论中所述,这可能是一个错误(或者不是-一个单元格具有许多属性,并且它们的相互作用没有完全指定)。棘手的解决方法-这里:触发虚假的编辑过渡或手动设置高度-经常需要。

在侦听所选属性时进行虚假编辑转换会破解该问题的事实表明,我们需要更新更改通知链中的图形“较早”(而不是updateItem),即所选内容发生更改时:

  • 连接(而不是手动监听)的方法是updateSelected(boolean)
  • 覆盖并根据需要更改图形

代码段:

@Override
public void updateSelected(boolean selected) {
    super.updateSelected(selected);
    setGraphic(selected ? selectedCell : defaultCell);
}



@Override
protected void updateItem(String s, boolean b) {
    super.updateItem(s, b);

    if(s == null || b) {
        setContentDisplay(ContentDisplay.TEXT_ONLY);
        setGraphic(null);
    }
    else {
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        setGraphic(isSelected() ? selectedCell : defaultCell);
    }
}

覆盖单元格的updateSelected(boolean)方法并根据需要设置单元格的图形: