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
(невидимые фигуры вынуждают смещения), но это, вероятно, не единственное место, где это произойдет.
Что происходит и как его избежать? В качестве альтернативы, как Я предполагаю, что я приближаюсь к этому неправильному, что было бы правильным путем? Другими словами, в чем вопрос, который я должен задать вместо этого?
Ответы
Ответ 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').