Weird recycle of cells in ListView - JavaFX

2019-02-28 14:02发布

What I understand from the documentation of JavaFX is that, when there are insufficient number of cells to fill the provided area of the ListView (or else like), new cells are generated. Otherwise, existing cells are reused, by calling updateItem method on each with the corresponding items.

I am implementing a chat application, in which the user writes something to the textarea, presses enter and the chat is appended to the history above the text area, very simple&classic. The history is a ListView, showing list of chats sent/received so far. So when the user presses enter, I add a new item to the underlying observable list, so it shows up in the ListView.

The problem is, when a new chat is appended to the ListView, there is some kind of a blink in the list, it takes a little time to update the items in the list. However, the newly added chat is at the end of the list, and it is not visible to the user (needs to scroll down, for now). So actually it is not needed to update the already visible cells, but it does. I tried to simplify the content of the updateItem method, but no way... It is always blinking.

Then I implemented a simple class like;

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;
    }
}

and then assigned ids to the generated cells by;

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);
        ....
    }
}

And I observed that, when a new item is added to the underlying list, the cells are reproduced, not recycled. (And probably this is reason of that blink, anyway)

Is it normal/logical/expected for cells to be reproduced? Is this a (known) bug? Have you ever seen this? Any workaround would be highly appreciated.

2条回答
▲ chillily
2楼-- · 2019-02-28 14:29

I created a similar test:

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);
        }

    }

}

In JavaFX 2.2 this seems to create an enormous number of ListCells (17 new cells every time I press the button; there are 16 cells visible) and calls updateItem(...) even more times. I guess that many of these cells go out of scope fairly quickly and are garbage collected.

Running the same code under JavaFX 8 shows quite different behavior; it creates 17 cells on the first button press and then creates no new ones after that. There are far fewer calls to updateItem(...), and none when the new cell is not displayed. This seems like it should be more efficient and is certainly more intuitive and closer in line with what I'd expect.

I do not see any of the "blinking" you report, though, either under JavaFX 2.2 or JavaFX 8; so it's not immediately clear that this is directly related to the cell creation and reuse behavior. It may be that the remainder of your updateItem(...) method can be made more efficient (e.g. cache complex graphics instead of recreating them each time), or that there is something else happening that is causing this.

Do you see the same thing under JavaFX 8?

查看更多
Fickle 薄情
3楼-- · 2019-02-28 14:35

It seems you don't need the ListView API for your application. ListView is not for just displaying widgets in a vertical area, but for binding a collection to a widget. In your case, I'd simply use a VBox wrapped in a ScrollPane and append new children as soon as they are acknowledged by the chat system.

In the comments you clarify that you chose the ListView API to improve memory usage. Well, storing a fixed number of widgets certainly reduces the need for allocating new memory. Still, at some point you'll fill up all the available space simply by storing the message objects.

If this project is a real app and not just a quick skectch, you need to improve the memory usage pattern by dedicating a certain amount of the heap to messages, and manually store/load to the persistence layer depending on the user interaction. You'll also need a pageable view, ie a rendering device that has the concept of rendering batches and smoothly handles the transitions between consecutive pages.

查看更多
登录 后发表回答