具有自定义CellFactory的ListView修剪不可见节点

时间:2021-04-12 05:28:48

My layout issue

I have a little issue with ListView and I'm not sure if it's because of some knowledge I missing or if my approach is flawed. Have to admit I'm not yet clear with how JavaFX handle the layout in the many possible cases.

我对ListView有一点问题,我不确定是不是因为我遗漏了一些知识,或者我的方法存在缺陷。不得不承认我还不清楚JavaFX如何在很多可能的情况下处理布局。

具有自定义CellFactory的ListView修剪不可见节点

The above screenshot shows the result I get twice with the exact same code, except that on the second one an invisible shape I use for coherent layout is made visible for debug.

上面的屏幕截图显示了我使用完全相同的代码获得两次的结果,除了在第二个屏幕截图中,我用于连贯布局的不可见形状对于调试是可见的。

The various classes involved by the CellFactory extend Group, I tried with some other Parent without much success so far.

CellFactory涉及的各个类扩展了Group,我和其他一些Parent一起尝试了迄今为止没有太大的成功。


How to reproduce

Rather than sharing my StarShape, StarRow and some other misc classes (I'd be happy to if requested) I wrote a sample reproducing the issue. The class extends Application and overrides the start(...) method as such:

而不是分享我的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();
}

where this::cellFactory is

这个:: 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);
            }
        }
    };
}

The above will display a ListView<Boolean> with black shapes in front of true items (because of the tabShape.setVisible(item); bit). The false items are looking like regular Label objects as if the invisible shape in their Group wasn't there (but it is).

上面将显示一个ListView ,在真实项目前面有黑色形状(因为tabShape.setVisible(item); bit)。伪项看起来像常规的Label对象,好像它们的Group中不可见的形状不存在(但它是)。


Closing comments

Debugging this, it turns out groups with the invisible shapes are given negative layoutX property values. Thus Label controls aren't aligned as I'd like them to be. It doesn't happen when I call setLayoutX and setLayoutY outside of a ListView (the invisible shapes do force offsets), but it's probably not the only place where it would happen.

对此进行调试,结果显示具有不可见形状的组被赋予负布局属性值。因此,Label控件没有像我希望的那样对齐。当我在ListView之外调用setLayoutX和setLayoutY时(不可见的形状强制偏移),它不会发生,但它可能不是它发生的唯一地方。

What's happening and how to avoid it? Alternatively, as I'm guessing I'm approaching this wrong, what'd be the right way? In other words, what is the question I should be asking instead of this?

发生了什么以及如何避免它?或者,因为我猜我正在接近这个错误,这是正确的方法吗?换句话说,我应该问的问题是什么?

2 个解决方案

#1


13  

Taking from @dlatikay's comment, instead of setting the placeholder items to invisible, you can render them transparent by setting their opacity to 0.0.

从@ dlatikay的评论中获取,不是将占位符项设置为不可见,而是可以通过将其不透明度设置为0.0来使它们透明。

Applied to the MCVE from your question, this would be done by replacing:

从你的问题应用到MCVE,这将通过替换:

tabShape.setVisible(item);

with:

有:

tabShape.setOpacity(item ? 1.0 : 0.0);

In terms of user experience, you could take this one step further. Instead of setting the "inactive" stars to fully transparent, you could set them to be near-transparent, as in this mockup (with opacity set to 0.1):

在用户体验方面,您可以更进一步。您可以将它们设置为接近透明,而不是将“非活动”星形设置为完全透明,就像在此模型中一样(不透明度设置为0.1):

具有自定义CellFactory的ListView修剪不可见节点

The benefits that I see are:

我看到的好处是:

  1. It indicates not only the rating of an item in the list, but also the maximum rating.
  2. 它不仅表示列表中项目的评级,还表示最大评级。
  3. It avoids awkward empty spaces for list items with zero stars.
  4. 它避免了零星的列表项的尴尬空白空间。

#2


4  

I'm guessing I'm approaching this wrong

我猜我正在接近这个错误

No, you're not. As with all layouts, there's often multiple ways to approach the same problem. Your approach is actually correct, and you're very close to a working solution.

不你不是。与所有布局一样,通常有多种方法可以解决同一问题。您的方法实际上是正确的,并且您非常接近工作解决方案。

You can achieve what you're after with a mere 1 line change. That is, changing the Group to an HBox.

只需1行更改即可实现您所追求的目标。也就是说,将集团改为HBox。

An HBox ensures that elements are ordered horizontally, one after another. They also allow invisible elements to still take up space.

HBox确保元素水平排列,一个接一个。它们还允许隐形元素占用空间。

I also commented out one line: label.setLayoutX(40). I did this because HBox will not respect this setting, and actually you don't need it to. It will automatically shift the elements horizontally by as much is required.

我还注释掉了一行: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);
    }
}

When I make those changes, your layout will render like so:

当我进行这些更改时,您的布局将呈现如下:

具有自定义CellFactory的ListView修剪不可见节点


Important: Your example and your screenshots are slightly different. You may want to use a VBox for your star example (V for 'vertical', H for 'horizontal').

重要提示:您的示例和屏幕截图略有不同。您可能希望使用VBox作为星形示例(V表示“垂直”,H表示“水平”)。

#1


13  

Taking from @dlatikay's comment, instead of setting the placeholder items to invisible, you can render them transparent by setting their opacity to 0.0.

从@ dlatikay的评论中获取,不是将占位符项设置为不可见,而是可以通过将其不透明度设置为0.0来使它们透明。

Applied to the MCVE from your question, this would be done by replacing:

从你的问题应用到MCVE,这将通过替换:

tabShape.setVisible(item);

with:

有:

tabShape.setOpacity(item ? 1.0 : 0.0);

In terms of user experience, you could take this one step further. Instead of setting the "inactive" stars to fully transparent, you could set them to be near-transparent, as in this mockup (with opacity set to 0.1):

在用户体验方面,您可以更进一步。您可以将它们设置为接近透明,而不是将“非活动”星形设置为完全透明,就像在此模型中一样(不透明度设置为0.1):

具有自定义CellFactory的ListView修剪不可见节点

The benefits that I see are:

我看到的好处是:

  1. It indicates not only the rating of an item in the list, but also the maximum rating.
  2. 它不仅表示列表中项目的评级,还表示最大评级。
  3. It avoids awkward empty spaces for list items with zero stars.
  4. 它避免了零星的列表项的尴尬空白空间。

#2


4  

I'm guessing I'm approaching this wrong

我猜我正在接近这个错误

No, you're not. As with all layouts, there's often multiple ways to approach the same problem. Your approach is actually correct, and you're very close to a working solution.

不你不是。与所有布局一样,通常有多种方法可以解决同一问题。您的方法实际上是正确的,并且您非常接近工作解决方案。

You can achieve what you're after with a mere 1 line change. That is, changing the Group to an HBox.

只需1行更改即可实现您所追求的目标。也就是说,将集团改为HBox。

An HBox ensures that elements are ordered horizontally, one after another. They also allow invisible elements to still take up space.

HBox确保元素水平排列,一个接一个。它们还允许隐形元素占用空间。

I also commented out one line: label.setLayoutX(40). I did this because HBox will not respect this setting, and actually you don't need it to. It will automatically shift the elements horizontally by as much is required.

我还注释掉了一行: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);
    }
}

When I make those changes, your layout will render like so:

当我进行这些更改时,您的布局将呈现如下:

具有自定义CellFactory的ListView修剪不可见节点


Important: Your example and your screenshots are slightly different. You may want to use a VBox for your star example (V for 'vertical', H for 'horizontal').

重要提示:您的示例和屏幕截图略有不同。您可能希望使用VBox作为星形示例(V表示“垂直”,H表示“水平”)。