JavaFX WhatApp-Like ConversationView

时间:2016-06-08 11:00:01

标签: javafx

我正在尝试在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();
}

1 个答案:

答案 0 :(得分:2)

您可以创建自定义控件以确定消息对齐和美观,例如像外观一样的气泡。作为HBoxVBox的粉丝,我建议将它们与SVGPath结合使用以装饰邮件。

SVGPath允许您通过提供有关线条,弧等的信息来绘制自定义形状。这些不是java独有的,因此有一些资源可用于查看一些基本/高级示例。我的建议是在此处阅读:SVGPath并使用TryitEditor进行实验

以下是两个简单示例: enter image description here

<小时/> 在布局消息时,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));
    }
}

输出:

enter image description here