这里有很多因素参与进来,使得自定义ListView水平滚动条行为变得难以处理。除此之外,对于ListView如何工作的常见误解也会导致其他问题。
需要解决的主要问题是:当垂直滚动条出现时,ListCells的宽度不会自动适应。因此,一旦出现垂直滚动条,内容就会突然变得太宽,无法适应在ListView的左边缘和垂直滚动条的左边缘之间,从而触发水平滚动条。还需考虑ListCell的默认填充以及ListView本身的边框宽度,以确定正确的绑定设置。
下面是扩展ListView的类:
public class WidthBoundList extends ListView {
private final BooleanProperty vbarVisibleProperty = new SimpleBooleanProperty(false);
private final boolean bindPrefWidth;
private final double scrollbarThickness;
private final double sumBorderSides;
public WidthBoundList(double scrollbarThickness, double sumBorderSides, boolean bindPrefWidth) {
this.scrollbarThickness = scrollbarThickness;
this.sumBorderSides = sumBorderSides;
this.bindPrefWidth = bindPrefWidth;
Platform.runLater(()->{
findScroller();
});
}
private void findScroller() {
if (!this.getChildren().isEmpty()) {
VirtualFlow flow = (VirtualFlow)this.getChildren().get(0);
if (flow != null) {
List<Node> flowChildren = flow.getChildrenUnmodifiable();
int len = flowChildren .size();
for (int i = 0; i < len; i++) {
Node n = flowChildren .get(i);
if (n.getClass().equals(VirtualScrollBar.class)) {
final ScrollBar bar = (ScrollBar) n;
if (bar.getOrientation().equals(Orientation.VERTICAL)) {
vbarVisibleProperty.bind(bar.visibleProperty());
bar.setPrefWidth(scrollbarThickness);
bar.setMinWidth(scrollbarThickness);
bar.setMaxWidth(scrollbarThickness);
} else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
bar.setPrefHeight(scrollbarThickness);
bar.setMinHeight(scrollbarThickness);
bar.setMaxHeight(scrollbarThickness);
}
}
}
} else {
Platform.runLater(()->{
findScroller();
});
}
} else {
Platform.runLater(()->{
findScroller();
});
}
}
public void bindWidthScrollCondition(Region node) {
node.maxWidthProperty().unbind();
node.prefWidthProperty().unbind();
node.maxWidthProperty().bind(
Bindings.when(vbarVisibleProperty)
.then(this.widthProperty().subtract(scrollbarThickness).subtract(sumBorderSides))
.otherwise(this.widthProperty().subtract(sumBorderSides))
);
if (bindPrefWidth) {
node.prefWidthProperty().bind(node.maxWidthProperty());
}
}
}
关于您的代码,您的绑定可能会引起问题。 ListCell的
updateItem()
方法不仅在创建ListCell时调用。 ListView可以包含相当大的数据列表,因此为了提高性能,只有滚动到视图中的ListCells(以及可能在其前后的一些)需要渲染其图形。
updateItem()
方法处理此操作。 在您的代码中,Region一遍又一遍地被创建,并且每个Region都被绑定到ListView的宽度上。 相反,应该绑定ListCell本身。
以下类扩展了ListCell,并在构造函数中调用了绑定HBox的方法:
public class BoundListCell extends ListCell<String> {
private final HBox hbox;
private final Label label;
public BoundListCell(WidthBoundList widthBoundList) {
this.setPadding(Insets.EMPTY);
hbox = new HBox();
label = new Label();
hbox.setPadding(new Insets(2, 4, 2, 4));
hbox.getChildren().add(label);
widthBoundList.bindWidthScrollCondition(this);
}
@Override
public void updateItem(String data, boolean empty) {
super.updateItem(data, empty);
if (empty || data == null) {
label.setText("");
setGraphic(null);
setText(null);
} else {
label.setText(data);
setGraphic(hbox);
}
}
}
WidthBoundList构造函数的scrollbarThickness
参数已设置为12。由于我的WidthBoundList在右侧和左侧有一个像素的边框,因此将sumBorderSides
参数设置为2。将bindPrefWidth
参数设置为true可防止水平滚动条完全显示(标签具有省略号,您可能添加到hbox中的任何非文本节点都将被简单地剪切)。将bindPrefWidth
设置为false以允许水平滚动条,并且使用这些适当的绑定应仅在需要时显示。实现如下:
private final WidthBoundList myListView = new WidthBoundList(12, 2, true);
public static void main(final String... a) {
Application.launch(a);
}
@Override
public void start(final Stage primaryStage) throws Exception {
myListView.setCellFactory(c -> new BoundListCell(myListView));
VBox vBox = new VBox();
vBox.setFillWidth(true);
vBox.setAlignment(Pos.CENTER);
vBox.setSpacing(5);
Button button = new Button("APPEND");
button.setOnAction((e)->{
myListView.getItems().add("THIS IS LIST ITEM NUMBER " + myListView.getItems().size());
});
vBox.getChildren().addAll(myListView, button);
myListView.maxWidthProperty().bind(vBox.widthProperty().subtract(20));
myListView.prefHeightProperty().bind(vBox.heightProperty().subtract(20));
primaryStage.setScene(new Scene(vBox, 200, 400));
primaryStage.show();
}