0

I have created an animation where prompt text will move up as text to the top of its text field when the field is focused, and move downwards when out of focus. However, if the user clicks too fast ( like keep pressing 'tab'), then the animation will the position of the prompt text will go chaos. How do i fix it?

import javafx.animation.TranslateTransition;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.util.Duration;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

public class Sing_Up implements Initializable {

    @FXML
    private TextField textField1;
    @FXML
    private TextField textField2;
    @FXML
    private TextField textField3;
    @FXML
    private TextField textField4;
    @FXML
    GridPane d;
    @FXML
    Button button;
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        button.setOnAction(event -> {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Home.fxml"));
            try {
                button.getScene().setRoot(loader.load());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        for (Node node : d.getChildren()) {
            System.out.println(node);
            // check if the node is a TextField
            if (node instanceof TextField) {
                TextField textField = (TextField) node;
                // apply the animate() method to the TextField
                animate(textField);
            }
    }}

    public void animate(TextField textField){

        Text promptText;
        String p=textField.getPromptText();
        // Create a new Text node to represent the prompt text
        promptText = new Text(textField.getPromptText());
        promptText.setFill(Color.RED);
        promptText.setFont(textField.getFont());
        GridPane parent = (GridPane) textField.getParent();
        GridPane.getRowIndex(textField1);
      
        // Animate the prompt text when the TextField receives focus
        textField.focusedProperty().addListener((observable, oldValue, newValue) -> {

            // Position the prompt text just above the TextField

            if(!parent.getChildren().contains(promptText)&& !(GridPane.getRowIndex(textField) ==null)){
            parent.add(promptText,0, GridPane.getRowIndex(textField));}

            promptText.setVisible(true);
                    if (newValue&&textField.getText().equals("")) {
                        promptText.setVisible(true);
                        TranslateTransition tt = new TranslateTransition(Duration.seconds(0.3), promptText);
                        tt.setByY(-35);
                        tt.play();
                    } else {
                        if (!newValue && textField.getText().isEmpty()){
                            TranslateTransition tt = new TranslateTransition(Duration.seconds(0.3), promptText);
                            tt.setByY (35);
                            tt.play();
                            tt.setOnFinished(event -> {
                                promptText.setVisible(false);

                            });};
;
    }
    ;});}}
ineedmoney
  • 63
  • 7
  • 2
    Use the same `TranslateTransition` instance and simply modify its `rate` to make it go forward or backward, based on the state. That way only one animation is modifying the position of the text at any given time. – Slaw Apr 10 '23 at 17:26
  • @Slaw it doesnt work =( – ineedmoney Apr 10 '23 at 17:41
  • I am not sure why you would choose to implement such an animation yourself when [MaterialFX already provides a variety of animated text fields](https://github.com/palexdev/MaterialFX#preview-gifs), as [seen here](https://camo.githubusercontent.com/b8aa3c104c8ad45e617e71686e8686a5a9a0af49c09bc84961ece78ee838b2f3/68747470733a2f2f696d6775722e636f6d2f585432695655372e676966). – jewelsea Apr 10 '23 at 18:19
  • @jewelsea yes brother, i want the textfield to have prompt text when input is empty, and floating text appears while clearing prompt text when focused. But for MFXtextfield, floating text will automatically be floating when prompt text exists, which is not what i wanted. Even if listener is added it wont work,i have pasted my code in MFX in the edited question , please check them out, thanks. – ineedmoney Apr 10 '23 at 19:00
  • 2
    Why don't you just keep references to both animations, and stop the one you don't need when you start the one you do need? – James_D Apr 10 '23 at 19:46
  • Related (not to animation) but to change the [default prompt text behavior in JavaFX](https://stackoverflow.com/questions/25125563/clear-prompt-text-in-javafx-textfield-only-when-user-starts-typing/25127540#25127540). – jewelsea Apr 10 '23 at 21:42

1 Answers1

4

While you can consider all the other suggestions in the comments, below is my version of addressing this issue.

Personally I would prefer to create a custom control that can handle this automatically irrespective of its Parent. Currently your logic seems to assume that the TextField is always in a GridPane.

I am considering the usage of Timeline rather than TranslateTransition, because of the fact that I only need the 'end value' for using Timeline. But with TranslateTransition, you need to provide the 'by' value or the 'from/to' values to run the transition (which is the reason it is getting messed up in your case).

Below is the demo of the custom TextField that demonstrates the behavior when rendered in different Parent nodes (here VBox & GridPane)

If you want to show the promptText animation conditionally, change the logic of the isPromptNeeded() method.

enter image description here

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

public class PromptTextFieldDemo extends Application {

    @Override
    public void start(final Stage stage) throws Exception {
        VBox root = new VBox();
        root.setPadding(new Insets(20));
        root.setSpacing(20);

        /* Add some fields in the GridPane */
        GridPane detailsGrid = new GridPane();
        detailsGrid.setHgap(20);
        detailsGrid.setVgap(20);
        detailsGrid.addRow(0, getField("First Name"), getField("Last Name"), getField("Age"));
        detailsGrid.addRow(1, getField("City"), getField("Company"));

        /* Add some fields in the VBox */
        root.getChildren().addAll(detailsGrid, getField("Address 1"), getField("Address 2"), getField("Address 3"));

        Scene scene = new Scene(root, 450, 300);
        stage.setScene(scene);
        stage.setTitle("PromptTextField Demo");
        stage.show();
    }

    private PromptTextField getField(String text) {
        PromptTextField textField = new PromptTextField();
        textField.setPromptText(text);
        return textField;
    }

    class PromptTextField extends TextField {
        private Text promptNode;

        private Timeline show;

        private Timeline hide;

        private boolean alwaysPrompt = true;

        public PromptTextField() {
            super();
        }

        public PromptTextField(String text) {
            super(text);
        }

        public void setAlwaysPrompt(final boolean alwaysPrompt) {
            this.alwaysPrompt = alwaysPrompt;
        }

        @Override
        protected void layoutChildren() {
            super.layoutChildren();
            if (getPromptText() != null && !getPromptText().isEmpty() && !getChildren().contains(getPromptNode())) {
                getPromptNode().setText(getPromptText());
                getPromptNode().setFont(getFont());
                getChildren().add(getPromptNode());
            }
        }

        public Text getPromptNode() {
            if (promptNode == null) {
                promptNode = new Text();
                promptNode.setFill(Color.RED);
                promptNode.setFont(getFont());
                promptNode.setVisible(false);
                /* Turn off the managed property, so that you control its position using layoutX/Y only. */
                promptNode.setManaged(false);
                promptNode.setLayoutX(8);
                promptNode.setLayoutY(17);

                focusedProperty().addListener((obs, old, focused) -> {
                    if (getPromptText() != null && !getPromptText().isEmpty()) {
                        if (focused && isPromptNeeded()) {
                            if (hide.getStatus() == Animation.Status.RUNNING) {
                                hide.stop();
                            }
                            promptNode.setVisible(true);
                            show.play();
                        } else if(!focused){
                            if (show.getStatus() == Animation.Status.RUNNING) {
                                show.stop();
                            }
                            hide.play();
                        }
                    }
                });

                final KeyFrame showKeyFrame = new KeyFrame(Duration.millis(300), new KeyValue(promptNode.layoutYProperty(), -5));
                show = new Timeline(showKeyFrame);

                final KeyFrame hideKeyFrame = new KeyFrame(Duration.millis(300), new KeyValue(promptNode.layoutYProperty(), 17));
                hide = new Timeline(hideKeyFrame);
                hide.setOnFinished(e -> promptNode.setVisible(false));
            }
            return promptNode;
        }

        private boolean isPromptNeeded(){
            /* Add your custom logic here: about when to show the prompt text animation */
            return alwaysPrompt || getText()==null || getText().isEmpty();
        }
    }
}
Sai Dandem
  • 8,229
  • 11
  • 26