我正在尝试在JavaFX中制作类似WhatsApp的对话视图。
为了使发送的消息显示在右侧,接收的消息显示在左侧,我不能使用TextArea。我该怎么做?我在没有TextArea的情况下尝试了GridPane,但它并没有让事情变得更容易。
此外,将控件设为静态是一种好习惯吗?
额外:如果你也可以帮我做文字背后的聊天泡泡,那就太好了。
这是我的代码:
public class ConversationView implements WhatAppView {
private static Label nameLabel, statusLabel;
private static TextField messageTextField;
static TextArea messagesTextArea;
private static GridPane conversationSection;
private static Label changeViewLink;
private static Button sendMsgButton;
// private static int rowIndex = 1;
public void showView() {
AppMain.stage.setResizable(false);
AppMain.stage.setWidth(350);
AppMain.stage.setHeight(550);
BorderPane rootPane = new BorderPane();
rootPane.setPadding(new Insets(5, 5, 5, 5));
final int sectionHeight = 55;
StackPane contactSection = new StackPane();
nameLabel = new Label("RW");
statusLabel = new Label("Online");
changeViewLink = new Label("Go Back");
changeViewLink.setStyle("-fx-text-fill: blue;");
changeViewLink.styleProperty().bind(
Bindings.when(changeViewLink.hoverProperty())
.then(new SimpleStringProperty("-fx-underline: true; -fx-text-fill: blue;"))
.otherwise(new SimpleStringProperty("-fx-underline: false; -fx-text-fill: blue;")));
changeViewLink.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
AppMain.changeView(new ChatsView());
}
});
contactSection.getChildren().addAll(nameLabel, statusLabel, changeViewLink);
StackPane.setAlignment(changeViewLink, Pos.TOP_RIGHT);
StackPane.setAlignment(statusLabel, Pos.BOTTOM_CENTER);
contactSection.setPrefHeight(sectionHeight);
conversationSection = new GridPane();
conversationSection.setStyle("-fx-background-image: url('whatsapp-wallpaper.jpg')");
messagesTextArea = new TextArea();
messagesTextArea.setEditable(false);
// conversationSection.getColumnConstraints().addAll(new
// ColumnConstraints(AppMain.stage.getWidth()/2 - 10), new
// ColumnConstraints(AppMain.stage.getWidth()/2 - 10));
conversationSection.add(messagesTextArea, 0, 0);
conversationSection.setPrefSize(AppMain.stage.getWidth(), AppMain.stage.getHeight());
// conversationSection.getStylesheets().add("conversation.css");
ScrollPane scroll = new ScrollPane();
scroll.setPrefSize(conversationSection.getWidth(), conversationSection.getHeight());
scroll.setContent(conversationSection);
FlowPane messageSection = new FlowPane();
sendMsgButton = new Button("_Send");
sendMsgButton.setDisable(true);
sendMsgButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
sendMsg();
}
});
sendMsgButton.setPrefHeight(sectionHeight);
Tooltip sendMsgToolTip = new Tooltip("Send Message");
Tooltip.install(sendMsgButton, sendMsgToolTip);
FlowPane.setMargin(sendMsgButton, new Insets(0, 0, 0, 5));
messageTextField = new TextField();
messageTextField.setPromptText("Type your message here...");
Platform.runLater(new Runnable() { // 100% focus
public void run() {
messageTextField.requestFocus();
}
});
messageTextField.setPrefWidth(AppMain.stage.getWidth() - AppMain.stage.getWidth() / 5);
messageTextField.setPrefHeight(sectionHeight);
messageTextField.setAlignment(Pos.TOP_LEFT);
messageTextField.setOnKeyTyped(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if (messageTextField.getText() != null && !messageTextField.getText().isEmpty()) {
sendMsgButton.setDisable(false);
} else {
sendMsgButton.setDisable(true);
}
}
});
messageTextField.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if (event.getCode().equals(KeyCode.ENTER) && messageTextField.getText() != null
&& !messageTextField.getText().isEmpty()) {
sendMsg();
}
}
});
messageSection.getChildren().add(messageTextField);
messageSection.getChildren().add(sendMsgButton);
messageSection.setPrefHeight(sectionHeight);
rootPane.setTop(contactSection);
rootPane.setCenter(conversationSection);
rootPane.setBottom(messageSection);
Scene scene = new Scene(rootPane);
AppMain.stage.setScene(scene);
AppMain.stage.setTitle("WhatsApp");
}
}
public class AppMain extends Application {
static Stage stage;
@Override
public void start(Stage primaryStage) throws Exception {
stage = primaryStage;
AppMain.stage.show();
changeView(new ConversationView());
}
public static void changeView(WhatAppView view) {
view.showView();
}
}
public interface WhatAppView {
public void showView();
}
答案 0 :(得分:2)
您可以创建自定义控件以确定消息对齐和美观,例如像外观一样的气泡。作为HBox
和VBox
的粉丝,我建议将它们与SVGPath
结合使用以装饰邮件。
SVGPath
允许您通过提供有关线条,弧等的信息来绘制自定义形状。这些不是java独有的,因此有一些资源可用于查看一些基本/高级示例。我的建议是在此处阅读:SVGPath并使用TryitEditor进行实验
<小时/> 在布局消息时,
VBox
就足够了。您可以将可查看的子项绑定到稍后可以迭代的ObservableList
个消息。这样做的另一个好处是,添加到列表将自动更新UI,并且您还可以在以后执行其他功能(例如删除,转发等)时对其进行迭代
我建议您阅读Bindings api,特别是bindContentBidirectional
了解有关此内容的更多信息
<小时/> 使用我的上述建议,我在下面写了一个小例子,你可以参考。它在视觉上并不令人印象深刻,但希望你可以从中得到一些想法,尤其是:
额外:如果你也可以帮我做文字背后的聊天泡泡吧 会很棒的。
消息/语音泡沫:
enum SpeechDirection{
LEFT, RIGHT
}
public class SpeechBox extends HBox{
private Color DEFAULT_SENDER_COLOR = Color.GOLD;
private Color DEFAULT_RECEIVER_COLOR = Color.LIMEGREEN;
private Background DEFAULT_SENDER_BACKGROUND, DEFAULT_RECEIVER_BACKGROUND;
private String message;
private SpeechDirection direction;
private Label displayedText;
private SVGPath directionIndicator;
public SpeechBox(String message, SpeechDirection direction){
this.message = message;
this.direction = direction;
initialiseDefaults();
setupElements();
}
private void initialiseDefaults(){
DEFAULT_SENDER_BACKGROUND = new Background(
new BackgroundFill(DEFAULT_SENDER_COLOR, new CornerRadii(5,0,5,5,false), Insets.EMPTY));
DEFAULT_RECEIVER_BACKGROUND = new Background(
new BackgroundFill(DEFAULT_RECEIVER_COLOR, new CornerRadii(0,5,5,5,false), Insets.EMPTY));
}
private void setupElements(){
displayedText = new Label(message);
displayedText.setPadding(new Insets(5));
displayedText.setWrapText(true);
directionIndicator = new SVGPath();
if(direction == SpeechDirection.LEFT){
configureForReceiver();
}
else{
configureForSender();
}
}
private void configureForSender(){
displayedText.setBackground(DEFAULT_SENDER_BACKGROUND);
displayedText.setAlignment(Pos.CENTER_RIGHT);
directionIndicator.setContent("M10 0 L0 10 L0 0 Z");
directionIndicator.setFill(DEFAULT_SENDER_COLOR);
HBox container = new HBox(displayedText, directionIndicator);
//Use at most 75% of the width provided to the SpeechBox for displaying the message
container.maxWidthProperty().bind(widthProperty().multiply(0.75));
getChildren().setAll(container);
setAlignment(Pos.CENTER_RIGHT);
}
private void configureForReceiver(){
displayedText.setBackground(DEFAULT_RECEIVER_BACKGROUND);
displayedText.setAlignment(Pos.CENTER_LEFT);
directionIndicator.setContent("M0 0 L10 0 L10 10 Z");
directionIndicator.setFill(DEFAULT_RECEIVER_COLOR);
HBox container = new HBox(directionIndicator, displayedText);
//Use at most 75% of the width provided to the SpeechBox for displaying the message
container.maxWidthProperty().bind(widthProperty().multiply(0.75));
getChildren().setAll(container);
setAlignment(Pos.CENTER_LEFT);
}
}
对话窗口:
public class ConversationView extends VBox{
private String conversationPartner;
private ObservableList<Node> speechBubbles = FXCollections.observableArrayList();
private Label contactHeader;
private ScrollPane messageScroller;
private VBox messageContainer;
private HBox inputContainer;
public ConversationView(String conversationPartner){
super(5);
this.conversationPartner = conversationPartner;
setupElements();
}
private void setupElements(){
setupContactHeader();
setupMessageDisplay();
setupInputDisplay();
getChildren().setAll(contactHeader, messageScroller, inputContainer);
setPadding(new Insets(5));
}
private void setupContactHeader(){
contactHeader = new Label(conversationPartner);
contactHeader.setAlignment(Pos.CENTER);
contactHeader.setFont(Font.font("Comic Sans MS", 14));
}
private void setupMessageDisplay(){
messageContainer = new VBox(5);
Bindings.bindContentBidirectional(speechBubbles, messageContainer.getChildren());
messageScroller = new ScrollPane(messageContainer);
messageScroller.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
messageScroller.setHbarPolicy(ScrollBarPolicy.NEVER);
messageScroller.setPrefHeight(300);
messageScroller.prefWidthProperty().bind(messageContainer.prefWidthProperty().subtract(5));
messageScroller.setFitToWidth(true);
//Make the scroller scroll to the bottom when a new message is added
speechBubbles.addListener((ListChangeListener<Node>) change -> {
while (change.next()) {
if(change.wasAdded()){
messageScroller.setVvalue(messageScroller.getVmax());
}
}
});
}
private void setupInputDisplay(){
inputContainer = new HBox(5);
TextField userInput = new TextField();
userInput.setPromptText("Enter message");
Button sendMessageButton = new Button("Send");
sendMessageButton.disableProperty().bind(userInput.lengthProperty().isEqualTo(0));
sendMessageButton.setOnAction(event-> {
sendMessage(userInput.getText());
userInput.setText("");
});
//For testing purposes
Button receiveMessageButton = new Button("Receive");
receiveMessageButton.disableProperty().bind(userInput.lengthProperty().isEqualTo(0));
receiveMessageButton.setOnAction(event-> {
receiveMessage(userInput.getText());
userInput.setText("");
});
inputContainer.getChildren().setAll(userInput, sendMessageButton, receiveMessageButton);
}
public void sendMessage(String message){
speechBubbles.add(new SpeechBox(message, SpeechDirection.RIGHT));
}
public void receiveMessage(String message){
speechBubbles.add(new SpeechBox(message, SpeechDirection.LEFT));
}
}
输出: