javafx: 如何自定义ListView的子项视图

ListView上显示的单元格称为ListCell,自定义ListView子项布局关键在于setCellFactory方法。

    /**
     * 设置要在ListView中使用的新单元格工厂。
     * 这迫使所有旧的{@link ListCell}被扔掉,而新的ListCell是用新的单元工厂创建的。
     */
    public final void setCellFactory(Callback<ListView<T>, ListCell<T>> value) {
    
    
        cellFactoryProperty().set(value);
    }

我们可以继承ListCell,对其再改造,从而获得美观的自定义子项视图。

对ListCell的继承

以下给出对ListCell进行一定封装的BaseCell

import javafx.fxml.FXMLLoader;
import javafx.scene.control.ListCell;
import javafx.util.Callback;

import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;

/**
 * 作为{@link javafx.scene.control.ListView}显示的子项Cell的基类,
 * 既可以简单展示文本,也可以展示更复杂的布局。
 * 在打算实现自定义子项视图时,子类必须作为FXML布局的视图控制器,
 * 并在{@link #bindData(T)}里为绑定的控件更换上面显示的数据。
 * @param <T> 展示在表格里的实体类类型
 */
public abstract class BaseCell<T> extends ListCell<T> implements Callback<Class<?>, Object> {
    
    

    private final URL fxmlURL;

    /**
     * 构造表上某一列的视图
     * @param fxmlURL 自定义控件的FXML资源定位符,如果为空,则简单展示文本
     */
    public BaseCell(URL fxmlURL) {
    
    
        this.fxmlURL = fxmlURL;
    }

    @Override
    protected void updateItem(T item, boolean empty) {
    
    
        super.updateItem(item, empty);
        if (empty || item == null) {
    
    
            // 如果这是一个空行
            setText(null);
            setGraphic(null);
        } else if (getGraphic() == null) {
    
    
            // 如果尚未创建需要显示的节点
            if (fxmlURL == null) {
    
    
                // 不设置自定义的节点,使用默认的显示方式显示信息
                setText(item.toString());
            } else {
    
    
                // 创建节点,并绑定数据
                try {
    
    
                    setGraphic(FXMLLoader.load(fxmlURL, null, null, this,
                            Charset.forName(FXMLLoader.DEFAULT_CHARSET_NAME)));
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
            bindData(item);
        } else {
    
    
            // 已经创建过节点,更新数据显示
            bindData(item);
        }

    }

    /**
     * 子类需要实现的用于绑定数据的方法。除了空行之外,该方法都会得到调用,可以使用这个方法覆盖默认行为。
     * 在新建Cell或者滚动{@link javafx.scene.control.ListView}时,这个方法会被反复调用以更新数据,
     * 但应用程序只会创建有限的Cell,此后持续复用它们,因此必须谨慎地处理Cell重用带来的一些问题,
     * 例如控件中会残留着已经消失在视野中的某行的信息。
     * @param item 当前位置需要展示的类,不会为空
     */
    public abstract void bindData(T item);

    @Override
    public Object call(Class<?> param) {
    
    
        return this;
    }
}

如何使用BaseCell

使用BaseCell,既可以编写自定义的FXML布局,继承BaseCell并作为FXML布局的控制器,也可以不编写复杂的布局,在构造BaseCell的时候直接传入null,这样BaseCell就只会展示简单的文本。

listview.setCellFactory(param -> new BaseCell<User>(null) {
    
    
            @Override
            public void bindData(User item) {
    
    
                // 覆盖默认行为,显示的文本设置为User的昵称,而不是User.toString()
                setText(item.getNickname());
            }
        });
public class ChatCell extends BaseCell<ChatContent> {
    
    
    @FXML
    private Text sender;
    @FXML
    private Text content;

    public ChatCell() {
    
    
        super(MainApplication.class.getResource("../res/fxml/ItemChat.fxml"));
    }

    @FXML
    private void initialize() {
    
    }
    
    ......
}

// 使用自定义布局
lvChatContent.setCellFactory(param -> new ChatCell());

猜你喜欢

转载自blog.csdn.net/chong_lai/article/details/114274734