如何使用listView组件来做聊天界面
1. 什么是CellFactory
?
在JavaFX中,控件(比如ListView
、TableView
等)用Cell
来显示每一条数据。
-
Cell
:代表这个单元格(即每个列表项)中显示的内容和样式。 -
CellFactory
:是一个工厂接口,负责创建和配置每个Cell
。
简单来说,
CellFactory
用于定义如何将数据对象(比如好友信息)转化为界面显示的单元格(Cell
)。
2. 为什么要自定义CellFactory
?
默认的Cell
只显示数据的toString()
方法的内容,
如果你需要自定义界面布局(比如显示头像、名字、状态指示等),
就需要自定义Cell
,通过实现自己的CellFactory
。
3. 如何自定义CellFactory
?
-
自定义
CellFactory
的步骤:-
调用
listView.setCellFactory()
; -
在
call()
中返回一个自定义的ListCell<T>
; -
重写
updateItem()
,设置每个单元格的内容。
-
详细的步骤:
1.创建一个消息属性类;
例如,以聊天室的聊天界面为例
import javafx.scene.image.Image;
public class TextMessage {
private final String content;
private final int MessageType;
private final String userName;
private final Image profilePicture;
private final boolean isSender;
public TextMessage(String content, int MessageType, String userName, Image profilePicture, boolean isSender) {
this.content = content;
this.MessageType = MessageType;
this.userName = userName;
this.profilePicture = profilePicture;
this.isSender = isSender;
}
public String getContent() {
return content;
}
public int getMessageType() {
return MessageType;
}
public String getUserName() {
return userName;
}
public Image getProfilePicture() {
return profilePicture;
}
public boolean getIsSender() {
return isSender;
}
}
2.创建Cell类(稍后写详细代码)
其中,ListCell后跟着的是你创建的属性类。重写方法后,我们将在else里写具体代码。
public class ChatTextMessageCell extends ListCell<TextMessage> {
@Override
protected void updateItem(TextMessage item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
}
}
}
3.在控制器类里初始化
首先,在类里添加如下代码:
private ObservableList<TextMessage> messages;
其中,ObservableList是 JavaFX 中用于实现数据绑定的重要接口。它允许 UI 控件自动响应数据变化。
将你的listView组件声明一下,如下,
@FXML public ListView<TextMessage> chatList;
初始化方法(注意:以initialize为名的无参方法无论加不加注解都会在应用启动时执行):
@FXML public void initialize(){
messages = FXCollections.observableArrayList();
chatList.setItems(messages);
chatList.setCellFactory(param -> new ChatTextMessageCell());
}
4.具体写Cell类
注意:这个类对于初学者来说并不好写,可能需调整多次才能达到预期。
对于javaFX有过了解的知道,VBox和HBox的区别,我们将在下面可能频繁使用这两个容器。
我们需要聊天界面显示的每条消息由头像,昵称,发出的消息等组成,其中,头像位于最左端或者最右端,昵称和发出的消息共同组成上下结构位于头像的另一端。也就是说,根容器,是需要展示左右两端的,我们使用HBox作为根容器,如下:
HBox root = new HBox();
之后,我们分别声明头像的组件和另一端的容器,又因为另一端是上下结构,我们使用VBox作为容器。如下:
ImageView imageView = new ImageView(item.getProfilePicture());
VBox messageBox = new VBox();
之后,我们将两者放入根容器中,如下:
root.getChildren().addAll(imageView, messageBox);
之后便是相同的操作了,依次处理上下结构即可。
显然,这样的结果不符合要求,我们还行对组件进行设置。在这便需要各位查阅官方文档了。
记得在最后加上setGraphic(root);应用设置!!!
完整代码引用:
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
public class ChatTextMessageCell extends ListCell<TextMessage> {
@Override
protected void updateItem(TextMessage item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
HBox root = new HBox();
if (item.getMessageType() == 1) {
// 时间消息样式保持不变
Label timeLabel = new Label(item.getContent());
timeLabel.setStyle("-fx-font-size: 10px; -fx-text-fill: gray;");
root.getChildren().add(timeLabel);
root.setAlignment(Pos.CENTER);
} else {
// ================== 头像区域 ==================
ImageView imageView = new ImageView(item.getProfilePicture());
imageView.setFitHeight(40);
imageView.setFitWidth(40);
imageView.setPreserveRatio(false);
imageView.setClip(new Circle(20, 20, 15));
// ================== 消息内容区域 ==================
VBox messageBox = new VBox();
// 昵称标签(添加左外边距贴近头像)
Label nameLabel = new Label(item.getUserName());
nameLabel.setStyle("-fx-font-size: 12px; -fx-text-fill: #666; /*-fx-padding: 0 0 2px 8px;*/");
// 消息气泡
if(item.getMessageType()==2){
HBox bubble = new HBox();
bubble.setMaxWidth(200);
bubble.setStyle(
"-fx-background-radius: 15px; " +
"-fx-padding: 8px 12px; " +
"-fx-background-color: " + (item.getIsSender() ? "#f5f5f5" : "#0099ff") + ";"
);
// 消息文本
Label messageLabel = new Label(item.getContent());
messageLabel.setStyle(
"-fx-font-size: 14px; " +
"-fx-text-fill: " + (item.getIsSender() ? "black" : "white") + ";"
);
messageLabel.setWrapText(true);
// ================== 布局组装 ==================
bubble.getChildren().add(messageLabel);
messageBox.getChildren().addAll(nameLabel, bubble);
// 根容器设置
root.setSpacing(5);
if (item.getIsSender()) {
messageBox.setAlignment(Pos.CENTER_LEFT);
root.setAlignment(Pos.CENTER_LEFT);
root.getChildren().addAll(imageView, messageBox);
} else {
messageBox.setAlignment(Pos.CENTER_RIGHT);
root.setAlignment(Pos.CENTER_RIGHT);
root.getChildren().addAll(messageBox, imageView);
}
}
}
setGraphic(root);
}
}
}
5.将listView的默认样式去除
在你的resources文件中,创建css文件,内容如下:
.list-view { /* 应用于整个ListView */
-fx-background-color: WHITE; /* ListView 背景透明 */
}
.list-cell {
-fx-background-color: transparent; /* 单元格背景透明 */
-fx-padding: 0; /* 移除默认内边距 */
-fx-border: none; /* 移除边框 */
-fx-focus-color: transparent; /* 移除焦点效果 */
-fx-control-inner-background: transparent; /* 解决部分主题背景问题 */
-fx-text-fill: black; /* 默认文本颜色,并保持 */
}
.list-cell:hover {
-fx-background-color: transparent; /* 悬停时透明 */
}
.list-cell:selected {
-fx-background-color: transparent; /* 选中时透明 */
}
.list-cell:selected:focused {
-fx-background-color: transparent; /* 选中并聚焦时透明 */
}
.list-cell:pressed {
-fx-background-color: transparent; /* 按下时透明 */
}
将此css文件导入即可。
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 900, 618);
// 加载 CSS 文件
scene.getStylesheets().add(Objects.requireNonNull(getClass().getResource("style.css")).toExternalForm());