How to get smooth animation with KeyPress event in

2019-01-15 17:18发布

问题:

I am trying to implement LEFT RIGHT movement of a rectangle shape in JavaFX. Below is my code:

public void start(Stage primaryStage) throws Exception {
    AnchorPane ancPane = new AnchorPane();      
    final Rectangle rect = new Rectangle();
    rect.setHeight(50);
    rect.setWidth(50);
    ancPane.getChildren().add(rect);
    Scene scene = new Scene(ancPane, 400, 200, Color.GREEN); 
    primaryStage.setScene(scene);
    primaryStage.show();


    scene.setOnKeyPressed(new EventHandler<KeyEvent>() {

        @Override
        public void handle(KeyEvent keyEvent) {
            System.out.println("hello");

            if(keyEvent.getCode().toString() == "RIGHT"){
                System.out.println("Move Right");
                TranslateTransition translateTransitionRight = new TranslateTransition();
                translateTransitionRight.setDuration(Duration.millis(200));
                translateTransitionRight.setNode(rect);
                translateTransitionRight.setFromX(rect.getTranslateX());
                translateTransitionRight.setToX(rect.getTranslateX()+30);
                translateTransitionRight.play();
            }

            if(keyEvent.getCode().toString() == "LEFT"){
                System.out.println("Move Left");
                TranslateTransition translateTransitionRight = new TranslateTransition();
                translateTransitionRight.setDuration(Duration.millis(200));
                translateTransitionRight.setNode(rect);
                translateTransitionRight.setFromX(rect.getTranslateX());
                translateTransitionRight.setToX(rect.getTranslateX()-30);
                translateTransitionRight.play();
            }               
        }
    });

}

Here when I press either LEFT/RIGHT key continuously (i.e. I don't released the key, I hold it for some times) the rectangle moves but not continuously. It pauses for a small fraction of time just after animation started. After the pause the animation continues smoothly.

How can I get rid of this pausing of animation with KeyEvents?

回答1:

I would use an AnimationTimer for moving the rectangle, and just update a property representing the velocity on key pressed or key released:

final Rectangle rect = ... ;

final double rectangleSpeed = 100 ; // pixels per second
final double minX = 0 ;
final double maxX = 800 ; // whatever the max value should be.. can use a property and bind to scene width if needed...
final DoubleProperty rectangleVelocity = new SimpleDoubleProperty();
final LongProperty lastUpdateTime = new SimpleLongProperty();
final AnimationTimer rectangleAnimation = new AnimationTimer() {
  @Override
  public void handle(long timestamp) {
    if (lastUpdateTime.get() > 0) {
      final double elapsedSeconds = (timestamp - lastUpdateTime.get()) / 1_000_000_000.0 ;
      final double deltaX = elapsedSeconds * rectangleVelocity.get();
      final double oldX = rect.getTranslateX();
      final double newX = Math.max(minX, Math.min(maxX, oldX + deltaX));
      rect.setTranslateX(newX);
    }
    lastUpdateTime.set(timestamp);
  }
};
rectangleAnimation.start();

scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
  @Override
  public void handle(KeyEvent event) {
    if (event.getCode()==KeyCode.RIGHT) { // don't use toString here!!!
      rectangleVelocity.set(rectangleSpeed);
    } else if (event.getCode() == KeyCode.LEFT) {
      rectangleVelocity.set(-rectangleSpeed);
    }
  }
});

scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
  @Override
  public void handle(KeyEvent event) {
    if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT) {
      rectangleVelocity.set(0);
    }
  }
});

UPDATE:

The AnimationTimer executes its handle method once each time a frame is rendered by the JavaFX mechanism. The long passed into the handle method is a timestamp of the render frame, in nanoseconds.

The way this works is that we keep track of the last update time. The handle(...) method computes the elapsed time since the last update, multiplies it by the rectangle's velocity, and updates the translateX of the rectangle by that amount. The AnimationTimer is always running, but initially the velocity is set to zero so the rectangle doesn't move.

The keyPressed handler simply changes the velocity: to a positive value if moving right and a negative value if moving left. The keyReleased handler sets the velocity back to zero.