listview with different cell height

mr mcwolf :

I'm trying to do a ListView in which the selected cell has a different UI (and a different height, respectively). The cells are declared in FXML and custom controls (DisplayRowDefault and DisplayRowSelected) are created that load the corresponding FXML files.

I also have a cell factory set up where I manage the rendering of the cell, depending on whether it is selected or not.

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);
            }
        }
    }
);

Everything works "perfectly" except that the cell size remains the same.

edit: I found a solution to the problem. When override computePrefHeight cells are resized correctly. The disadvantage of this method is that the prefHeight must be explicitly specified.

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);
        }
    }
);

edit: 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();
    }
}
kleopatra :

As noted in the comments this might be a bug (or not - a cell has many properties and their interplay is not completely specified). Hacky workarounds - here: triggering fake edit transitions or manually setting the height - often are needed.

The fact that a fake edit transition on listening to the selected property hacks the problem indicates that we need to update the graphic "earlier" (than updateItem) in the change notification chain, that is when the selected changes:

  • the method to hook into (instead of manually listening) is updateSelected(boolean)
  • override and change the graphic as needed

A code snippet:

@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);
    }
}

to override the cell's updateSelected(boolean) method and set the cell's graphic as needed:

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=140698&siteId=1