我从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);
....
}
}
我观察到,当新项目被添加到基础列表时,单元格被复制,而不是被回收。 (无论如何,这可能是眨眼的原因)
是否正常/逻辑/期望细胞被复制?这是一个已知的错误?你见过这个吗?任何解决方法都将受到高度赞赏。
答案 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,你需要通过将一定数量的堆专用于消息来改进内存使用模式,并根据用户交互手动存储/加载到持久层。您还需要一个可分页视图,即具有渲染批次概念并平滑处理连续页面之间转换的渲染设备。