我正在尝试做一个ListView
,其中所选单元格具有不同的UI(分别具有不同的高度)。这些单元格在FXML
中声明,并创建了自定义控件(DisplayRowDefault
和DisplayRowSelected
)来加载相应的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();
}
}
答案 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)方法并根据需要设置单元格的图形: