JavaFX - Diferentes layouts em um ListCell personalizado

Estou construindo um aplicativo de bate-papo usando JavaFX. Agora, para exibir mensagens de bate-papo, eu uso um simplesListView doChatItems com uma fábrica de células personalizada,ChatCell.

ChatItem poderia ser umChatLabel (anúncios do servidor, alteração de status etc.) ou umChatMessage (mensagem enviada por si ou por terceiros). EChatMessage pode ser estendido para mostrar vários conteúdos (apenasChatTextMessage eChatImageMessage para agora)

Minha célula personalizada verifica o tipo / características do item e fornece a visualização adequada de acordo.

Eu gostaria de mostrar um carimbo de data / hora em cadaChatMessage veja no canto inferior direito do balão de bate-papo. MAS, se o texto for apenas uma linha, quero que o carimbo de data e hora seja exibido em um espaço extra à direita do texto. (como o WhatsApp ou a maioria dos aplicativos de bate-papo).

Aqui está uma demonstração:

Minha implementação atual (mostrada na demonstração) usa uma propriedade booleana emChatMessage que determina se a visualização deve ser multilinha ou não. Ele verifica todas as atualizações. Também adiciono um ouvinte aListView largura e chamadarefresh() quando isso muda. Tudo isso parece excessivo. E não acho que adicionar uma propriedade "relacionada à exibição" a um "modelo" seja um bom design. Sem mencionar que, na primeira atualização do item, o carimbo de data e hora não está no local correto por algum motivo.

Como posso fazer isso corretamente?

ATUALIZAÇÃO 1: Eu adicionei um extrator de propriedade para oListView itens e adicionou um ouvinte para oListView largura que chamaupdateItem na largura muda. Agora não preciso ligarrefresh()

Aqui estáChatCell classe:

public class ChatCell extends ListCell<ChatItem> {

    private ChatItemView view;
    private InvalidationListener listViewListener;
    private InvalidationListener listViewWidthListener;

    public ChatCell() {
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

        listViewWidthListener = e -> {
            updateItem(getItem(), isEmpty());
        };

        listViewListener = e -> {
            if(getView() == null) return;
            getView().unbindWidth();
            if(listViewProperty().get() != null) {
                getView().bindWidth(listViewProperty().get().widthProperty());
                listViewProperty().get().widthProperty().addListener(listViewWidthListener);
                listViewWidthListener.invalidated(listViewProperty().get().widthProperty());
            }
        };

    }

    public ChatItemView getView() {
        return view;
    }

    private void initChatLabelView() {
        view = new ChatLabelView();
    }

    private void initChatMessageView(ChatMessage message) {
        view = new ChatMessageViewWrapper(message);
    }


    private void resetView() {
        if(view == null) return;
        view.unbindWidth();
        listViewProperty().removeListener(listViewListener);
        if(getListView() != null)
            getListView().widthProperty().removeListener(listViewWidthListener);
        view = null;
    }

    private void newViewCreated() {
        listViewProperty().addListener(listViewListener);
        setGraphic(view);
        listViewListener.invalidated(listViewProperty());
    }

    @Override
    protected void updateItem(ChatItem item, boolean empty) {
        super.updateItem(item, empty);
        if(item == null || empty) {
            setGraphic(null);
            setText(null);
            resetView();
        } else {
            // First-level comparison: Label vs. Message
            if(item.getViewType() == ViewType.LABEL) {
                if(view == null || !(view instanceof ChatLabelView)) {
                    resetView();
                    initChatLabelView();
                    newViewCreated();
                }

            }

            else if(item.getViewType() == ViewType.MESSAGE) {
                ChatMessage message = (ChatMessage) item;
                if(view == null || !(view instanceof ChatMessageViewWrapper)) {
                    resetView();
                    initChatMessageView(message);
                    newViewCreated();
                }
                else {
                    ChatMessageViewWrapper bubble = (ChatMessageViewWrapper) view;
                    // Second-level comparison: Self-message vs. other user message,
                    // Text vs. Image (or file), multiline vs. oneline 
                    if(  (bubble.getContentType() != message.getContentType())
                            || (bubble.isSelf() ^ message.isSelf()) 
                            || (bubble.isMultiline() ^ message.isMultiline())) {
                        resetView();
                        initChatMessageView(message);
                        newViewCreated();
                    }

                }

            }

            else {
                return;
            }

            view.setItem(item);
        }
    }

    @Override
    public Orientation getContentBias() {
        return Orientation.HORIZONTAL;
    }

}

questionAnswers(0)

yourAnswerToTheQuestion