Подтвердить что ты не робот

ListView с пользовательскими CellFactory обрезает невидимые узлы

Мой вопрос с макетом

У меня есть небольшая проблема с ListView, и я не уверен, что это из-за каких-то знаний, которые я потерял, или если мой подход ошибочен. Должен признаться, я еще не понял, как JavaFX обрабатывает макет во многих возможных случаях.

ListView урезает мою попытку макета

Приведенный выше снимок экрана показывает результат, который я получаю дважды с помощью того же кода, за исключением того, что на втором невидимая форма, которую я использую для когерентного макета , становится видимой для отладки.

Различные классы, участвующие в CellFactory, расширяются Group, я пробовал с некоторыми другими Parent без особого успеха до сих пор.


Как воспроизвести

Вместо того, чтобы делиться моими StarShape, StarRow и некоторыми другими классами misc (я был бы рад, если это было необходимо), я написал образец, воспроизводящий проблему. Класс расширяет Application и переопределяет метод start(...) как таковой:

@Override
public void start(Stage primaryStage) throws Exception {
    final StackPane root = new StackPane();
    final Scene scene = new Scene(root, 400, 600);

    final ListView<Boolean> listView = new ListView<>();
    listView.setCellFactory(this::cellFactory);

    for (int i = 0; i < 5 ; i++) {
        listView.getItems().add(true);
        listView.getItems().add(false);
    }

    root.getChildren().add(listView);

    primaryStage.setScene(scene);
    primaryStage.setTitle("ListView trims the invisible");
    primaryStage.show();
}

где this::cellFactory -

private ListCell<Boolean> cellFactory(ListView<Boolean> listView) {
    return new ListCell<Boolean>() {
        @Override
        protected void updateItem(Boolean item, boolean empty) {
            super.updateItem(item, empty);

            if (empty || item == null) {
                setText(null);
            } else {
                final Rectangle tabShape = new Rectangle();
                tabShape.setHeight(20);
                tabShape.setWidth(40);
                tabShape.setVisible(item);

                final Label label = new Label(item.toString());
                label.setLayoutX(40);

                final Group cellRoot = new Group();
                cellRoot.getChildren().add(tabShape);
                cellRoot.getChildren().add(label);

                setGraphic(cellRoot);
            }
        }
    };
}

В приведенном выше примере будет отображаться ListView<Boolean> с черными фигурами перед элементами true (из-за бит tabShape.setVisible(item);). Элементы false выглядят как обычные объекты Label, как если бы невидимая форма в их Group не была (но это так).


Закрытие комментариев

Отлаживая это, оказывается, что группы с невидимыми фигурами получают отрицательные значения свойства layoutX. Таким образом, элементы управления Label не выравниваются, как хотелось бы. Этого не происходит, когда я вызываю setLayoutX и setLayoutY вне ListView (невидимые фигуры вынуждают смещения), но это, вероятно, не единственное место, где это произойдет.

Что происходит и как его избежать? В качестве альтернативы, как Я предполагаю, что я приближаюсь к этому неправильному, что было бы правильным путем? Другими словами, в чем вопрос, который я должен задать вместо этого?

4b9b3361

Ответ 1

Взяв из @dlatikay комментарий, вместо того, чтобы указывать элементы-заполнители невидимыми, вы можете сделать их прозрачными, установив их непрозрачность на 0.0.

Применительно к MCVE из вашего вопроса это будет сделано путем замены:

tabShape.setVisible(item);

с:

tabShape.setOpacity(item ? 1.0 : 0.0);

Что касается пользовательского опыта, вы можете сделать это еще на один шаг. Вместо того, чтобы "неактивные" звезды были полностью прозрачными, вы могли бы установить их почти прозрачными, как в этом макете (с непрозрачностью, установленной на 0.1):

mockup

Преимущества, которые я вижу:

  • Указывает не только рейтинг элемента в списке, но и максимальный рейтинг.
  • Это позволяет избежать неудобных пустых пространств для элементов списка с нулевыми звездами.

Ответ 2

Я предполагаю, что приближаюсь к этому неправильному

Нет, это не так. Как и во всех макетах, часто существует несколько способов приблизиться к одной и той же проблеме. Ваш подход действительно правильный, и вы очень близки к рабочему решению.

Вы можете добиться того, что вам нужно, всего лишь с одной строкой. То есть, изменив Group на HBox.

An HBox гарантирует, что элементы упорядочены по горизонтали, один за другим. Они также позволяют невидимым элементам по-прежнему занимать пространство.

Я также прокомментировал одну строку: label.setLayoutX(40). Я сделал это, потому что HBox не будет уважать эту настройку, и на самом деле вам это не нужно. Он будет автоматически перемещать элементы по горизонтали на столько, сколько требуется.

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

    if (empty || item == null) {
        setText(null);
    }
    else {
        final Rectangle tabShape = new Rectangle();
        tabShape.setHeight(20);
        tabShape.setWidth(40);
        tabShape.setVisible(item);

        final Label label = new Label(item.toString());
        //label.setLayoutX(40);

        final HBox cellRoot = new HBox();
        cellRoot.getChildren().add(tabShape);
        cellRoot.getChildren().add(label);

        setGraphic(cellRoot);
    }
}

Когда я делаю эти изменения, ваш макет будет выглядеть так:

введите описание изображения здесь


Важно. Ваш пример и ваши скриншоты немного отличаются. Вы можете использовать VBox для своего примера на звезду (V для 'vertical', H для 'horizontal').