前言:
在写这个作业之前,尝试在JavaFX中添加全局快捷键,测试了大概5个小时,到处找教程换版本,结果最后还是没找到支持Java8以上的(也有可能是我自己的问题),最后只能退而求其次,用jintellitype这个库来在原生swing里面添加了全局快捷键,算是圆了我对SAO Utils的一个执念吧,虽然实际上相差甚远。
作业:
1.安装向导
提供一个setup.jar,模拟常见的软件安装交互特性。
主要功能:上一步下一步的页面切换,按钮交互响应,页面创建
2.广播
制作一个可以向子窗体同步信息的父窗体,内容由文本框输入,父窗体上放一个广播按钮和一个子窗体创建按钮,广播按钮点击后可以让父窗体输入文本显示到所有子窗体上,子窗体创建按钮点击后创建新的窗体(点击一次创建一次)。
主要功能:文本输入,信息同步,按钮交互响应,窗体创建
3.监控
制作一个可以收集自己创建的子窗体信息的父窗体,父窗体上有一个子窗体创建按钮和一个显示框,子窗体创建按钮点击后创建一个子窗体,显示框内显示当前已经创建的子窗体的名字(依次命名为1,2,3...)和每个子窗体上按钮被点击的次数;每个子窗体中心有一个点击按钮,点击后主窗体更新内容。
主要功能:窗体创建,信息传递,按钮交互响应,计数计算
4.双向实时通讯
创建一个主窗体和一个从窗体,主窗体上显示"已完成了 %",从窗体上显示一个进度条(初始为0,在下方显示百分比)和两个按钮,一个增加按钮一个减少按钮,点击增加按钮,进度条增加1%,点击减少,进度条减少1%,从窗体的进度条和主窗体显示的 %相关联,同样的,主窗体上也有这两个按键。
主要功能:按钮响应,信息双向同步,计数,线程安全(我在主窗体的按钮上加上了长按加速功能,方便调试,不然点来点去好难受)
5.微信群模拟
用JavaFX模拟微信群聊功能。
程序运行时可以动态创建多个窗体,也就是说先有一个主窗体,在创建从窗体时,用户可以选择一个群加入(相当于一个class来将从窗体分到不同的类别里,参见html的class),每个窗体代表一个用户(每个用户按照创建窗体的先后顺序有一个id),使用窗体背景色区分开各个群的成员。一个用户发送的消息只有他所属的群内的其他用户能看到。【我新加入了历史记录的模块,但是似乎效果并不是很好,当前的同步方式是一次性同步所有历史数据,显然当数据量较大时不太合适,而且太低效了(这个好改,但是懒)】
JavaFX学习记录
1.布局·
VBox垂直布局,HBox水平布局
2.线程安全
确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行。
关键字:synchronized,lock,volatile
完成(慢慢更新ing,注解有时间再写当做复习)
1.安装向导
java
package com.example.test10;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SetupWizard extends Application {
private Stage stage;
private int currentPage;
private VBox pageContainer;
private final Button backButton = new Button("Back");
private final Button nextButton = new Button("Next");
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
stage = primaryStage;
stage.setTitle("Setup Wizard");
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 300);
// 创建页面容器
pageContainer = new VBox();
pageContainer.setPadding(new Insets(20));
pageContainer.setAlignment(Pos.CENTER_LEFT);
// 创建页面
VBox page1 = createPage1();
VBox page2 = createPage2();
VBox page3 = createPage3();
// 添加页面到容器
pageContainer.getChildren().addAll(page1, page2, page3);
// 设置当前页面
currentPage = 0;
showCurrentPage();
// 创建按钮容器
HBox buttonContainer = new HBox();
buttonContainer.setPadding(new Insets(20));
buttonContainer.setAlignment(Pos.BOTTOM_RIGHT);
// 设置布局
HBox.setMargin(backButton, new Insets(0, 10, 0, 0));
backButton.setOnAction(event -> showPreviousPage());
nextButton.setOnAction(event -> showNextPage());
buttonContainer.getChildren().addAll(backButton, nextButton);
root.setCenter(pageContainer);
root.setBottom(buttonContainer);
nextButton.setDisable(true);
stage.setScene(scene);
stage.show();
}
private VBox createPage1() {
VBox page = new VBox();
page.setSpacing(10);
Label label = new Label("Please read and accept the agreement:");
CheckBox checkBox = new CheckBox("I agree to the above terms and conditions");
checkBox.setOnAction(event -> nextButton.setDisable(!checkBox.isSelected()));
page.getChildren().addAll(label, checkBox);
return page;
}
private VBox createPage2() {
VBox page = new VBox();
page.setSpacing(10);
Label label = new Label("Please enter the installation directory:");
TextField directoryField = new TextField();
directoryField.textProperty().addListener((observable, oldValue, newValue) ->
nextButton.setDisable(newValue.isEmpty()));
page.getChildren().addAll(label, directoryField);
return page;
}
private VBox createPage3() {
VBox page = new VBox();
page.setSpacing(10);
Label label = new Label("Please select an option:");
Button exitButton = new Button("Exit");
exitButton.setOnAction(event -> stage.close());
HBox buttonContainer = new HBox();
buttonContainer.setAlignment(Pos.BOTTOM_RIGHT);
buttonContainer.getChildren().addAll(backButton, exitButton);
page.getChildren().addAll(label, buttonContainer);
return page;
}
private void showPreviousPage() {
if (currentPage > 0) {
currentPage--;
showCurrentPage();
}
}
private void showNextPage() {
if (currentPage < pageContainer.getChildren().size() - 1) {
currentPage++;
showCurrentPage();
nextButton.setDisable(true);
}
}
private void showCurrentPage() {
pageContainer.getChildren().forEach(page -> page.setVisible(false));
pageContainer.getChildren().get(currentPage).setVisible(true);
backButton.setDisable(currentPage == 0);
nextButton.setDisable(currentPage == pageContainer.getChildren().size() - 1);
}
}
2. 广播
java
package com.example.test11;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class ParentWindow extends Application {
private final List<ChildWindow> childWindows = new ArrayList<>();
private double childWindowOffsetY = 0;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Parent Window");
// 创建文本输入框
TextField inputTextField = new TextField();
inputTextField.setPromptText("输入要广播的文本");
// 创建广播按钮
Button broadcastButton = new Button("广播");
broadcastButton.setOnAction(e -> broadcastMessage(inputTextField.getText()));
// 创建子窗体创建按钮
Button createChildButton = new Button("创建子窗体");
createChildButton.setOnAction(e -> createChildWindow(primaryStage));
// 创建按钮布局
HBox buttonLayout = new HBox(10);
buttonLayout.getChildren().addAll(broadcastButton, createChildButton);
// 创建父窗体布局
VBox parentLayout = new VBox(10);
parentLayout.setPadding(new Insets(10));
parentLayout.getChildren().addAll(inputTextField, buttonLayout);
primaryStage.setScene(new Scene(parentLayout, 500, 350));
primaryStage.show();
}
// 广播消息给所有子窗体
private void broadcastMessage(String message) {
for (ChildWindow childWindow : childWindows) {
childWindow.showMessage(message);
}
}
// 创建子窗体
private void createChildWindow(Stage parentStage) {
ChildWindow childWindow = new ChildWindow();
childWindows.add(childWindow);
// 为了方便观察效果
// 我把子窗体放在了在父窗体的右侧
// 并设置childWindowOffsetY作为子窗体的垂直偏移量
double parentX = parentStage.getX();
double parentY = parentStage.getY();
double parentWidth = parentStage.getWidth();
double parentHeight = parentStage.getHeight();
childWindow.setX(parentX + parentWidth + 2);
childWindow.setY(parentY-parentHeight/2+childWindowOffsetY);
childWindowOffsetY += 120;
childWindow.show();
}
// 子窗体类
private static class ChildWindow extends Stage {
private final TextArea messageArea;
public ChildWindow() {
setTitle("Child Window");
// 消息显示区域
messageArea = new TextArea();
messageArea.setEditable(false);
// 子窗体布局
VBox childLayout = new VBox(10);
childLayout.setPadding(new Insets(10));
childLayout.getChildren().addAll(messageArea);
setScene(new Scene(childLayout, 250, 180));
}
// 在子窗体上显示消息
public void showMessage(String message) {
Platform.runLater(() -> messageArea.appendText(message + "\n"));
}
}
}
3.监控
java
package com.example.text12;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class ParentWindow extends Application {
private int childWindowCount = 0;
private double childWindowOffsetY = 0;
private final List<ChildWindow> childWindows = new ArrayList<>();
private Label infoLabel;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Parent Window");
// 创建子窗体创建按钮
Button createChildButton = new Button("创建子窗体");
createChildButton.setOnAction(e -> createChildWindow(primaryStage));
// 创建信息显示标签
infoLabel = new Label("已创建子窗体信息:");
// 创建父窗体布局
VBox parentLayout = new VBox(10);
parentLayout.setPadding(new Insets(10));
parentLayout.getChildren().addAll(createChildButton, infoLabel);
primaryStage.setScene(new Scene(parentLayout, 500, 350));
primaryStage.show();
}
// 创建子窗体
private void createChildWindow(Stage parentStage) {
childWindowCount++;
String childWindowName = String.valueOf(childWindowCount);
ChildWindow childWindow = new ChildWindow(childWindowName, this);
childWindows.add(childWindow);
double parentX = parentStage.getX();
double parentY = parentStage.getY();
double parentWidth = parentStage.getWidth();
double parentHeight = parentStage.getHeight();
childWindow.setX(parentX + parentWidth + 2);
childWindow.setY(parentY-parentHeight/2+childWindowOffsetY);
childWindowOffsetY += 180;
childWindow.show();
updateInfoLabel();
}
// 更新信息显示标签和父窗体的总点击次数
private void updateInfoLabel() {
StringBuilder sb = new StringBuilder();
int totalClickCount = 0;
for (ChildWindow childWindow : childWindows) {
sb.append("子窗体").append(childWindow.getName()).append(": ").append(childWindow.getClickCount()).append("次点击\n");
totalClickCount += childWindow.getClickCount();
}
sb.append("总点击次数: ").append(totalClickCount);
Platform.runLater(() -> infoLabel.setText("已创建子窗体信息:\n" + sb));
}
// 子窗体类
private static class ChildWindow extends Stage {
private final String name;
private int clickCount = 0;
public ChildWindow(String name, ParentWindow parentWindow) {
this.name = name;
setTitle("Child Window " + name);
// 创建点击按钮
Button clickButton = new Button("点击");
clickButton.setOnAction(e -> {
clickCount++;
parentWindow.updateInfoLabel();
});
// 创建子窗体布局
VBox childLayout = new VBox(10);
childLayout.setAlignment(Pos.CENTER);
childLayout.setPadding(new Insets(10));
childLayout.getChildren().addAll(clickButton);
setScene(new Scene(childLayout, 250, 180));
}
// 获取子窗体名称
public String getName() {
return name;
}
// 获取子窗体的点击次数
public int getClickCount() {
return clickCount;
}
}
}
4. 双向实时通讯
java
package com.example.text13;
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private static Main instance; // 单例实例
private int progress = 0; // 进度值
private final Object lock = new Object(); // 同步锁对象
private Label progressLabel; // 主窗体的进度文本标签
private PauseTransition increaseTimer; // 增加定时器
private PauseTransition decreaseTimer; // 减少定时器
public static Main getInstance() {
return instance;
}
public int getProgress() {
return progress;
}
public Object getLock() {
return lock;
}
public void increaseProgress() {
if (progress < 100) {
progress++;
}
}
public void decreaseProgress() {
if (progress > 0) {
progress--;
}
}
@Override
public void start(Stage primaryStage) {
instance = this; // 设置单例实例
// 创建主窗体
primaryStage.setTitle("主窗体");
progressLabel = new Label("已完成了 0%");
progressLabel.setAlignment(Pos.CENTER);
Button increaseButton = new Button("增加");
Button decreaseButton = new Button("减少");
increaseButton.setOnAction(e -> {
increaseProgress();
updateProgressLabel();
});
decreaseButton.setOnAction(e -> {
decreaseProgress();
updateProgressLabel();
});
increaseButton.setOnMousePressed(e -> increaseTimer.playFromStart());
increaseButton.setOnMouseReleased(e -> increaseTimer.stop());
decreaseButton.setOnMousePressed(e -> decreaseTimer.playFromStart());
decreaseButton.setOnMouseReleased(e -> decreaseTimer.stop());
increaseTimer = createTimer(increaseButton, true);
decreaseTimer = createTimer(decreaseButton, false);
HBox buttonLayout = new HBox(10);
buttonLayout.setAlignment(Pos.CENTER);
buttonLayout.getChildren().addAll(increaseButton, decreaseButton);
VBox mainLayout = new VBox(10);
mainLayout.setAlignment(Pos.CENTER);
mainLayout.getChildren().addAll(progressLabel, buttonLayout);
Scene mainScene = new Scene(mainLayout, 450, 300);
primaryStage.setScene(mainScene);
primaryStage.show();
// 创建从窗体
Thread syncThread = getThread(primaryStage);
syncThread.start();
}
private Thread getThread(Stage primaryStage) {
ProgressBarWindow progressBarWindow = new ProgressBarWindow();
Stage secondaryStage = getStage(primaryStage, progressBarWindow);
secondaryStage.show();
// 同步主窗体和从窗体的进度
Thread syncThread = new Thread(() -> {
try {
while (true) {
synchronized (lock) {
Platform.runLater(() -> {
progressBarWindow.updateProgress(progress);
progressBarWindow.updatePercentage(progress);
updateProgressLabel();
});
lock.wait(100);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
syncThread.setDaemon(true);
return syncThread;
}
private static Stage getStage(Stage primaryStage, ProgressBarWindow progressBarWindow) {
Stage secondaryStage = new Stage();
secondaryStage.setTitle("从窗体");
Scene secondaryScene = new Scene(progressBarWindow.getLayout(), 250, 180);
secondaryStage.setScene(secondaryScene);
double primaryX = primaryStage.getX();
double primaryY = primaryStage.getY();
double primaryWidth = primaryStage.getWidth();
double primaryHeight = primaryStage.getHeight();
secondaryStage.setX(primaryX + primaryWidth + 2);
secondaryStage.setY((primaryY+primaryHeight)/2);
return secondaryStage;
}
private void updateProgressLabel() {
progressLabel.setText("已完成了 " + progress + "%");
}
private PauseTransition createTimer(Button button, boolean increase) {
PauseTransition timer = new PauseTransition(Duration.millis(100));
timer.setOnFinished(e -> {
synchronized (lock) {
if (increase) {
increaseProgress();
} else {
decreaseProgress();
}
updateProgressLabel();
}
timer.playFromStart();
});
button.setOnMouseClicked(e -> {
if (e.getClickCount() > 1) {
timer.stop();
}
});
return timer;
}
public static void main(String[] args) {
launch(args);
}
}
class ProgressBarWindow {
private final VBox layout;
private final ProgressBar progressBar;
private final Label percentageLabel;
public ProgressBarWindow() {
progressBar = new ProgressBar(0);
percentageLabel = new Label("0%");
VBox.setMargin(progressBar, new javafx.geometry.Insets(10));
VBox.setMargin(percentageLabel, new javafx.geometry.Insets(10));
Button increaseButton = new Button("增加");
Button decreaseButton = new Button("减少");
increaseButton.setOnAction(e -> {
synchronized (Main.getInstance().getLock()) {
Main.getInstance().increaseProgress();
updateProgress(Main.getInstance().getProgress());
updatePercentage(Main.getInstance().getProgress());
}
});
decreaseButton.setOnAction(e -> {
synchronized (Main.getInstance().getLock()) {
Main.getInstance().decreaseProgress();
updateProgress(Main.getInstance().getProgress());
updatePercentage(Main.getInstance().getProgress());
}
});
HBox buttonLayout = new HBox(10);
buttonLayout.setAlignment(Pos.CENTER);
buttonLayout.getChildren().addAll(increaseButton, decreaseButton);
layout = new VBox(10);
layout.setAlignment(Pos.CENTER);
layout.getChildren().addAll(progressBar, percentageLabel, buttonLayout);
}
public VBox getLayout() {
return layout;
}
public void updateProgress(int progress) {
progressBar.setProgress(progress / 100.0);
}
public void updatePercentage(int progress) {
percentageLabel.setText(progress + "%");
}
}
5.微信群模拟
java
package com.example.test14;
import javafx.scene.control.Alert;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class WeChatGroupSimulation extends Application {
private static List<GroupChatMember> groupChatMembers; // 存储群聊成员
private double childWindowOffsetY = 0; // 用来调节新窗体位置
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
groupChatMembers = new ArrayList<>();//创建一个空的ArrayList对象,用于存储群聊成员。
// 创建主窗体
createMainWindow(primaryStage);
primaryStage.setTitle("WeChat Group Simulation");
primaryStage.show();
}
private void createMainWindow(Stage primaryStage) {
VBox root = new VBox();//垂直布局
root.setAlignment(Pos.CENTER); // 居中对齐
root.setSpacing(10); // 垂直间距10像素
root.setPadding(new Insets(10)); // 内边距10像素
Button addButton1 = new Button("加入1群");
addButton1.setOnAction(event -> createGroupChatMember("1群", Color.RED, primaryStage));
root.getChildren().add(addButton1);
Button addButton2 = new Button("加入2群");
addButton2.setOnAction(event -> createGroupChatMember("2群", Color.GREEN, primaryStage));
root.getChildren().add(addButton2);
// 加入群组3的按钮
Button addButton3 = new Button("加入3群");
addButton3.setOnAction(event -> createGroupChatMember("3群", Color.BLUE, primaryStage));
root.getChildren().add(addButton3);
Scene scene = new Scene(root, 300, 200);
primaryStage.setScene(scene);
}
// 创建群聊成员窗口
private void createGroupChatMember(String groupName, Color backgroundColor, Stage parentStageStage) {
GroupChatMember member = new GroupChatMember(groupName);
// 将已存在的群聊成员的历史消息合并到新成员的历史消息中
for (GroupChatMember existingMember : groupChatMembers) {
if (existingMember.getGroupName().equals(groupName)) {
member.getHistory().addAll(existingMember.getHistory());
break;
}
}
Stage stage = new Stage();
stage.setTitle(groupName);
VBox root = new VBox();
root.setStyle("-fx-background-color: " + toRGBCode(backgroundColor)); // 背景颜色
root.setSpacing(10);
root.setPadding(new Insets(10));
TextArea historyArea = new TextArea(); // 创建文本区域用于显示历史消息
historyArea.setPrefHeight(500);
historyArea.setEditable(false);
ListView<String> messageList = new ListView<>(); // 创建列表视图用于显示消息列表
TextArea inputArea = new TextArea(); // 创建文本区域作为输入框
inputArea.setPromptText("Enter your message..."); // 输入框提示文本
inputArea.setPrefHeight(60);
root.getChildren().addAll(historyArea, messageList, inputArea); // 将历史消息区域、消息列表和输入框添加到root中
Scene scene = new Scene(root, 300, 400);
// 新窗口初始位置(主要是为了自己方便一点,懒得动鼠标)
double primaryX = parentStageStage.getX();
double primaryY = parentStageStage.getY();
double primaryWidth = parentStageStage.getWidth();
double primaryHeight = parentStageStage.getHeight();
stage.setX(primaryX + primaryWidth );
stage.setY(primaryY-primaryHeight/2+ childWindowOffsetY);
childWindowOffsetY += 100;
stage.setScene(scene);
// 模拟发送消息
inputArea.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ENTER) {
String message = inputArea.getText().trim();
inputArea.clear();
if (!message.isEmpty()) {
member.sendMessage(message); // 发送消息给成员对象
broadcastMessage(member, message); // 通过当前成员对象,广播消息给其他成员
messageList.scrollTo(messageList.getItems().size() - 1); // 聚焦到最后一条消息
} else {
showErrorAlert();
}
}
});
// 监听新消息
member.setOnMessageReceived(new MessageReceivedListener() {
@Override
public void onMessageReceived(String message) {
// 更新消息列表
Platform.runLater(() -> messageList.getItems().add(message));
}
@Override
public void onHistoryUpdated(String historyText) {
// 更新历史消息
Platform.runLater(() -> historyArea.setText(historyText));
}
});
groupChatMembers.add(member);
stage.show();
}
// 错误提示窗口
private void showErrorAlert() {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Error");
alert.setHeaderText(null);
alert.setContentText("Message cannot be empty");
alert.showAndWait();
}
// 广播消息给同一群组的其他成员,也就是其他成员获得sender的message
private void broadcastMessage(GroupChatMember sender, String message) {
for (GroupChatMember member : groupChatMembers) {
if (member != sender && member.getGroupName().equals(sender.getGroupName())) {
member.receiveMessage(sender.getUsername(), message);
}
}
}
// 颜色转换:RGB
private String toRGBCode(Color color) {
int r = (int) (color.getRed() * 255);
int g = (int) (color.getGreen() * 255);
int b = (int) (color.getBlue() * 255);
return String.format("#%02x%02x%02x", r, g, b);
}
}
// 群聊成员类
class GroupChatMember {
private static int memberID = 0;
private final String groupName;
private final String username;
private final List<String> history;
private MessageReceivedListener listener;
public GroupChatMember(String groupName) {
this.groupName = groupName;
this.username = "成员" + getNextMemberID();
this.history = new ArrayList<>();
}
private static synchronized int getNextMemberID() {
return ++memberID;
}
public String getGroupName() {
return groupName;
}
public String getUsername() {
return username;
}
public List<String> getHistory() {
return history;
}
public void setOnMessageReceived(MessageReceivedListener listener) {
this.listener = listener;
updateHistory();
}
public void sendMessage(String message) {
history.add(username + ": " + message);
if (listener != null) {
listener.onMessageReceived(username + ": " + message);
updateHistory();
}
}
public void receiveMessage(String sender, String message) {
history.add(sender +": "+ message);
if (listener != null) {
listener.onMessageReceived(sender +": "+ message);
updateHistory();
}
}
private void updateHistory() {
StringBuilder builder = new StringBuilder();
for (String message : history) {
builder.append(message).append("\n");
}
if (listener != null) {
listener.onHistoryUpdated(builder.toString());
}
}
}
// 监听消息接收和历史消息更新的接口
interface MessageReceivedListener {
void onMessageReceived(String message);
void onHistoryUpdated(String historyText);
}