效果:
这个界面实现并不是很难,之前想要用Swing
实现一个,但是写到一般发现自己是在不是很适应Swing
的GUI界面制作的模式,前面学了不算短的时间的前端,还是比较适应这种模式。
其次,根据我的使用观感来说,Swing
的各种组件的特性方面似乎有些落后,自适应性、样式调整等几个方面似乎不是很与时俱进,当然,个人意见,如果是我学艺不精,还请不吝赐教。
左边是一个成员列表,右上是消息区,右下输入区,成员列表和输入区添加较简单。
难得就是消息区,首先是滚动区域,很明显外面需要套一个ScrollPane
,但是此时有一个小功能需要实现,就是当我们聊天的时候界面要始终保持在最下方,但是当我们看历史消息的时候不能总是直接拉到最低部。实现这个功能最大的困难在于,按照正常想法应该是判断添加时是否在最底部,如果是就在添加完成后将滚轮保持在底部,可问题在于渲染时间,很难保证在渲染后再移动滚轮,我的解决办法是添加滚轮移动事件监听,判断移动种类,得到是否要保持滚轮在最底部。
第二层需要在里面一行一行的添加消息,我弟一瞬间想到的是VBox
,但其实用VBox
的花会有一个很大的问题,就是消息必定是动态添加的,加上ScorollPane
显示滚动条的条件,这就涉及到第二层组件的高度动态变化。但VBox
高度无法自动扩展,在这种情况下,因为消息高度在显示前无法获取,VBox
的动态变化管理也确实有难度,所以在最后把第二层组件换成了FlowPane
,这部分解决。
接下来就是每个消息单元,消息单元使用HBox
完美解决,目前每个单元里包含头像,消息两个部分,根据是否是自己发出的消息判断得到消息单元内的组件排列顺序以及对齐方式就可以得到靠左靠右的效果。
最后的部分就是这个气泡了,基于Label
组件,添加圆角效果-fx-background-radius: 8px;
,添加背景颜色-fx-background-color: rgb(179,231,244);
就已经差不多了,接下来是那个三角,三角可以直接使用JavaFx的图形组件Polygon
制作一个三角形并调整相对位置,完美解决
接下来奉上代码:
FXML部分:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.FlowPane?>
<AnchorPane focusTraversable="true" prefHeight="700.0" prefWidth="520.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="UI.chatroom.ChatroomController">
<children>
<TextArea fx:id="message" layoutX="139.0" layoutY="618.0" prefHeight="82.0" prefWidth="381.0" promptText="在此输入消息......" wrapText="true" />
<ScrollPane fx:id="scrollPane" hbarPolicy="NEVER" layoutX="140.0" prefHeight="619.0" prefWidth="380.0">
<content>
<FlowPane fx:id="messagesList" prefHeight="45.0" prefWidth="366.0" />
</content></ScrollPane>
<ScrollPane hbarPolicy="NEVER" layoutX="-1.0" layoutY="-1.0" prefHeight="703.0" prefWidth="140.0" style="-fx-background: rgb(39,43,45);">
<content>
<FlowPane fx:id="membersList" prefHeight="46.0" prefWidth="125.0" />
</content>
</ScrollPane>
</children>
</AnchorPane>
Java代码部分:
import database.struct.Member;
import database.struct.Message;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Font;
import java.net.URL;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.Objects;
import java.util.ResourceBundle;
public class ChatroomController implements Initializable {
@FXML
private FlowPane membersList;
@FXML
private TextArea message;
@FXML
private FlowPane messagesList;
@FXML
private ScrollPane scrollPane;
public static int MyId;
public static ArrayList<Member> members = new ArrayList<Member>();
public ArrayList<Message> messages = new ArrayList<Message>();
private boolean last = true;
@Override
public void initialize(URL url, ResourceBundle resources) {
membersList.setPadding(new Insets(5));
VBox.setVgrow(messagesList, Priority.ALWAYS);
message.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.ENTER) {
String text = message.getText();
message.setText("");
Message theMessage = new Message(MyId, text, new Timestamp(new Date().getTime()));
messages.add(theMessage);
addMessageBox(theMessage);
}
}
});
scrollPane.vvalueProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
if (last) {
scrollPane.setVvalue(1.0);
last = false;
}
}
});
getPersonalData();
addMembers();
for (Message message: messages) {
addMessageBox(message);
}
scrollPane.setVvalue(1);
}
private void getPersonalData() {
MyId = 2;
members.add(new Member(1, "avatar.png", "一号", true));
members.add(new Member(2, "avatar.png", "二号", true));
members.add(new Member(3, "avatar.png", "三号", true));
members.add(new Member(4, "avatar.png", "四号", true));
members.add(new Member(5, "avatar.png", "五号", true));
Message message1 = new Message(1, "那天你消失在人海里", new Timestamp(new Date().getTime()));
Message message2 = new Message(4, "你的背影沉默得让人恐惧 你说的那些问题 我回答得很坚定", new Timestamp(new Date().getTime()));
Message message3 = new Message(2, "偏偏那个时候我最想你", new Timestamp(new Date().getTime()));
Message message4 = new Message(2, "我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,我不曾爱过你", new Timestamp(new Date().getTime()));
Message message5 = new Message(5, "我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,我不曾爱过你我不曾爱过你,我自己骗自己,", new Timestamp(new Date().getTime()));
Message message6 = new Message(2, "我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,我不曾爱过你我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,", new Timestamp(new Date().getTime()));
Message message7 = new Message(4, "我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,我不曾爱过你我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,那天你消失在人海里", new Timestamp(new Date().getTime()));
Message message8 = new Message(4, "我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,我不曾爱过你我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,那天你消失在人海里那天你消失在人海里", new Timestamp(new Date().getTime()));
Message message9 = new Message(4, "我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,我不曾爱过你我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,那天你消失在人海里那天你消失在人海里那天你消失在人海里那天你消失在人海里", new Timestamp(new Date().getTime()));
Message message10 = new Message(4, "我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,我不曾爱过你我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,那天你消失在人海里那天你消失在人海里", new Timestamp(new Date().getTime()));
Message message11 = new Message(4, "我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,我不曾爱过你我不曾爱过你,我自己骗自己,已经给你写了信,又被我丢进海里,那天你消失在人海里那天你消失在人海里", new Timestamp(new Date().getTime()));
messages.add(message1);
messages.add(message2);
// messages.add(message3);
messages.add(message4);
messages.add(message5);
messages.add(message6);
messages.add(message7);
messages.add(message8);
messages.add(message9);
}
private void addMembers() {
for (Member member: members) {
Image headImg = new Image(Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(member.getHead())));
ImageView head = new ImageView();
head.setImage(headImg);
head.setFitWidth(40);
head.setFitHeight(40);
Label name = new Label(member.getName());
name.setTextFill(Color.rgb(255, 255, 255));
Label status = new Label(member.getStatus() ? "在线" : "离线");
status.setTextFill(Color.rgb(255, 255, 255));
VBox info = new VBox(8, name, status);
info.setPadding(new Insets(2, 0, 0, 8));
membersList.getChildren().add(new HBox(head, info));
}
}
private Member getMemberById(int senderId) {
for (Member member: members) {
if (member.getId() == senderId) {
return member;
}
}
return null;
}
private void addMessageBox(Message message) {
Member sender = getMemberById(message.getSenderId());
assert sender != null;
Image headImg = new Image(Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(sender.getHead())));
ImageView head = new ImageView();
head.setImage(headImg);
head.setFitWidth(40);
head.setFitHeight(40);
Label messageBubble = new Label(message.getMessage());
messageBubble.setWrapText(true);
messageBubble.setMaxWidth(220);
messageBubble.setStyle("-fx-background-color: rgb(179,231,244); -fx-background-radius: 8px;");
messageBubble.setPadding(new Insets(6));
messageBubble.setFont(new Font(14));
HBox.setMargin(messageBubble, new Insets(8, 0, 0, 0));
boolean isMine = message.getSenderId() == MyId;
double[] points;
if (!isMine) {
points = new double[]{
0.0, 5.0,
10.0, 0.0,
10.0, 10.0
};
} else {
points = new double[]{
0.0, 0.0,
0.0, 10.0,
10.0, 5.0
};
}
Polygon triangle = new Polygon(points);
triangle.setFill(Color.rgb(179,231,244));
HBox messageBox = new HBox();
messageBox.setPrefWidth(366);
messageBox.setPadding(new Insets(10, 5, 10, 5));
if (isMine) {
HBox.setMargin(triangle, new Insets(15, 10, 0, 0));
messageBox.getChildren().addAll(messageBubble, triangle, head);
messageBox.setAlignment(Pos.TOP_RIGHT);
} else {
HBox.setMargin(triangle, new Insets(15, 0, 0, 10));
messageBox.getChildren().addAll(head, triangle, messageBubble);
}
last = scrollPane.getVvalue() == 1.0;
messagesList.getChildren().add(messageBox);
}
}