【问题标题】:Weird recycle of cells in ListView - JavaFXListView 中奇怪的单元格回收 - JavaFX
【发布时间】:2013-12-18 14:59:50
【问题描述】:

我从 JavaFX 的文档中了解到,当单元格数量不足以填充提供的 ListView 区域(或其他类似区域)时,会生成新的单元格。否则,现有单元格会被重用,方法是在每个单元格上调用 updateItem 方法以及相应的项目。

我正在实现一个聊天应用程序,其中用户向文本区域写入内容,按下回车键并将聊天附加到文本区域上方的历史记录中,非常简单和经典。历史是一个 ListView,显示到目前为止发送/接收的聊天列表。所以当用户按下回车键时,我会在底层的 observable 列表中添加一个新项目,所以它会显示在 ListView 中。

问题是,当一个新的聊天被追加到 ListView 时,列表中有某种闪烁,更新列表中的项目需要一点时间。 但是,新添加的聊天位于列表末尾,用户看不到(现在需要向下滚动)。所以实际上不需要更新已经可见的单元格,但确实需要。我试图简化updateItem方法的内容,但是没办法……它总是在闪烁。

然后我实现了一个简单的类,例如;

public class IdGenerator {
    private static IdGenerator instance = new IdGenerator();
    private int id = 0;

    public static IdGenerator getInstance() {
        return instance;
    }

    public synchronized int getId() {
        return ++id;
    }
}

然后将id分配给生成的单元格;

public class CommunicationEntityCell extends ListCell<CommunicationEntity> {

    ...
    private int id = IdGenerator.getInstance().getId();
    ....

    @Override
    protected synchronized void updateItem(CommunicationEntity entity, boolean empty) {
        super.updateItem(entity, empty);
        System.out.println(id);
        ....
    }
}

我观察到,当一个新项目被添加到基础列表中时,单元格被复制,而不是被回收。 (无论如何,这可能是眨眼的原因)

细胞被复制是否正常/合乎逻辑/预期?这是一个已知的错误?你见过这个吗?任何解决方法都将受到高度赞赏。

【问题讨论】:

    标签: listview javafx-2


    【解决方案1】:

    我创建了一个类似的测试:

    import javafx.application.Application;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.ListCell;
    import javafx.scene.control.ListView;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    import javafx.util.Callback;
    
    public class ListCellReuseTest extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            final BorderPane root = new BorderPane();
            final ListView<String> list = new ListView<>();
            final Button addButton = new Button("Add message");
            addButton.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {
                    int count = list.getItems().size() + 1 ;
                    System.out.println("Creating message "+count);
                    list.getItems().add("Message "+count);
                }
            });
            list.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
    
                @Override
                public ListCell<String> call(ListView<String> listView) {
                    return new TestListCell();
                }
    
            });
    
            root.setCenter(list);
            root.setBottom(addButton);
            primaryStage.setScene(new Scene(root, 200, 400));
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    
        public static class TestListCell extends ListCell<String> {
            private static int nextId ;
            private final int id ;
            public TestListCell() {
                id = ++nextId ;
            }
            @Override
            public void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                setText(item);
                System.out.println(id);
            }
    
        }
    
    }
    

    在 JavaFX 2.2 中,这似乎创建了大量的 ListCells(每次按下按钮时都会创建 17 个新单元格;有 16 个单元格可见)并且调用 updateItem(...) 的次数甚至更多。我猜其中许多单元格很快就会超出范围并被垃圾收集。

    在 JavaFX 8 下运行相同的代码会表现出完全不同的行为;它在第一次按下按钮时创建 17 个单元格,然后不再创建新单元格。对 updateItem(...) 的调用要少得多,并且在不显示新单元格时也没有。这似乎应该更有效,当然更直观,更符合我的预期。

    但是,在 JavaFX 2.2 或 JavaFX 8 下,我没有看到您报告的任何“闪烁”;所以目前还不清楚这是否与单元格的创建和重用行为直接相关。可能是您的 updateItem(...) 方法的其余部分可以提高效率(例如,缓存复杂的图形而不是每次都重新创建它们),或者是其他原因导致了这种情况。

    你在 JavaFX 8 下看到同样的事情吗?

    【讨论】:

    • 我没有安装JavaFX8,但会尝试。
    【解决方案2】:

    您的应用程序似乎不需要ListView API。 ListView 不仅仅用于在垂直区域显示小部件,还用于将集合绑定到小部件。在您的情况下,我只需使用 VBox 包裹在 ScrollPane 中,并在聊天系统确认后立即追加新的孩子。

    在 cmets 中,您阐明您选择了 ListView API 来提高内存使用率。好吧,存储固定数量的小部件肯定会减少分配新内存的需要。不过,在某些时候,您只需存储消息对象即可填满所有可用空间。

    如果这个项目是一个真正的应用程序而不仅仅是一个快速的草图,你需要通过将一定数量的堆专用于消息来改善内存使用模式,并根据用户交互手动存储/加载到持久层.您还需要一个可分页视图,即具有批量渲染概念并平滑处理连续页面之间的转换的渲染设备。

    【讨论】:

    • 在我之前的类似应用程序中,我按照您的建议使用了一个容器,但是这种方法的问题是,当历史越来越大时,它会占用大量内存。历史记录中的每个条目不仅仅是文本,它包括时间标签、日期标签、发件人标签、一些图像/效果等。所以我认为 ListView 会是一个更好的解决方案。 gemsea 对这里问题的第一条评论启发了我:stackoverflow.com/questions/18094607/…
    • 问题是你仍然有“错误”的内存使用模式。我的意思是,如果你将消息(字符串+时间戳)保存在内存中,内存消耗仍然会随着时间的推移而增加。如果这是一个真正的应用程序,你必须为模型实现一个环形缓冲区和一个用于渲染的可分页视图,还要处理一些持久性提供程序的序列化/反序列化。否则你的应用程序无论如何都会崩溃:也许稍后,但在某些时候它会耗尽内存。我的观点是,要么你做一个真正的应用,要么根本不在乎
    猜你喜欢
    • 2012-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-21
    相关资源
    最近更新 更多