4

After a wrong insert i want to shake a Textfield. For that i have code a static shake animation

public static void shake(Node node) {
  TranslateTransition tt = new TranslateTransition(Duration.millis(50), node);
  tt.setByX(10f);
  tt.setCycleCount(2);
  tt.setAutoReverse(true);
  tt.playFromStart();
}

This animation is called in a ChangeListener when the input is wrong. This works fine but if the user type wrong characters very fast, the TextField is moving to the right. Is there a way to do a repositioning? Or is there a better way to do this?

kayf
  • 87
  • 1
  • 11

2 Answers2

3

Don't create a new transition every time you want to shake the field, otherwise the field will shake while it is already shaking, the outcome of which would be difficult to predict but probably pretty undesirable.

The other thing you need to do is to setFromX(0) for the translate transition. This is actually pretty important because what happens with a translate transition is that, when it stops, the translateX value for the node remains at whatever it was when the transition stopped.

When you invoke playFromStart multiple times while the transition is playing, the transition will be stopped again and then started from the beginning. If you don't have a fromX, then the beginning will be wherever the translateX value last ended up at (which might not be what you want at all and, after shaking, the item might start moving to seemingly random positions on the screen). However, if you have a fromX, then the beginning translateX value will always start from an untranslated position.

import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ShakenNotStirred extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        TextField field = new TextField();
        Shaker shaker = new Shaker(field);
        field.textProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                try {
                    Integer.parseInt(newValue);
                } catch (NumberFormatException e) {
                    shaker.shake();
                }
            }
        });
        StackPane layout = new StackPane(field);
        layout.setPadding(new Insets(20));

        stage.setScene(new Scene(layout));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    class Shaker {
        private TranslateTransition tt;

        public Shaker(Node node) {
            tt = new TranslateTransition(Duration.millis(50), node);
            tt.setFromX(0f);
            tt.setByX(10f);
            tt.setCycleCount(2);
            tt.setAutoReverse(true);
        }

        public void shake() {
            tt.playFromStart();
        }
    }
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
0

Same way i thinking. Thanks jewelsea!

The solution for me is to make the TranslateTransition static and use it inside the static method like this:

private static TranslateTransition tt;
public static TranslateTransition shake(Node node) {
  if (tt == null || tt.getNode() != node)
  {
      tt = new TranslateTransition(Duration.millis(50), node);
  }
  tt.setByX(10f);
  tt.setCycleCount(2);
  tt.setAutoReverse(true);
  if (tt.getStatus() == Status.STOPPED)
  {
      tt.playFromStart();
  }
  return tt;
}

In this way, a shake will only perform when the previous is stopped. And the transition only changed if it is NULL or another Node.

kayf
  • 87
  • 1
  • 11