ListView中的细胞回收奇怪 - JavaFX

时间:2013-12-18 14:59:51

标签: listview javafx-2

我从JavaFX的文档中理解的是,当没有足够数量的单元格来填充ListView的提供区域(或者更喜欢)时,会生成新的单元格。否则,通过使用相应的项目调用updateItem方法来重用现有单元格。

我正在实现一个聊天应用程序,用户在其中向textarea写入内容,按回车键,聊天将附加到文本区域上方的历史记录中,非常简单和经典。历史记录是ListView,显示到目前为止发送/接收的聊天列表。因此,当用户按下回车键时,我会将新项添加到基础可观察列表中,因此它会显示在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);
        ....
    }
}

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

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

2 个答案:

答案 0 :(得分:2)

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

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下看到了同样的事情吗?

答案 1 :(得分:1)

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

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

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