1 Reply Latest reply: Feb 21, 2013 1:21 PM by James_D RSS

    Slide animation flicker

    992527
      I have a menu that slides out from the top of the scene if the user hovers over the top 3% of a `WebView`, which otherwise occupies the entire height of the scene. I can't get rid of 'flicker' during the sliding animation.

      --------

      Heres an idea of the FXML for the controller:
          <fx:root type="javafx.scene.layout.StackPane" minHeight="768.0" minWidth="1024.0" xmlns:fx="http://javafx.com/fxml">
            <children>
              <WebView fx:id="wv" onKeyPressed="#onKeyDown" onMouseMoved="#onMouseMove" prefHeight="768.0" prefWidth="1024.0" />
              <VBox fx:id="vbox_slideview" fillWidth="true" maxHeight="326.0" maxWidth="1024.0" minHeight="326.0" minWidth="1024.0" prefHeight="326.0" prefWidth="1024.0" visible="false" StackPane.alignment="TOP_CENTER">
                ...
                <StackPane.margin>
                 <Insets top="-326.0" />
                </StackPane.margin>
              </VBox>
            </children>
          </fx:root>
      Note that the `root` element is my controller class that extends `StackPane`.

      Heres the basic onMouseMove (for now I just want it to slide down correctly):
           public void onMouseMove(MouseEvent e)
           {
                if (e.getSceneY() < 0.03 * stage.getScene().getHeight())
                {
                     this.vbox_slideview.setVisible(true);
                     TimelineBuilder.create().keyFrames(
                          new KeyFrame(
                               Duration.millis(300),
                               new KeyValue(this.vbox_slideview.layoutYProperty(),
                               0)))
                          .build().play();
                }
           }
      This flickers so I add a "blocking mechanism" to stop the new animation from running while another one is one the way:
           Timeline     anim     = null;
           boolean          blocked     = false;
      
           public boolean isBlocked()
           {
                return blocked;
           }
      
           public void setBlocked(boolean blocked)
           {
                this.blocked = blocked;
           }
      
           public void onMouseMove(MouseEvent e)
           {
                if (isBlocked())
                     return;
      
                setBlocked(true);
      
                if (e.getSceneY() < 0.03 * stage.getScene().getHeight())
                {
                     this.vbox_slideview.setVisible(true);
                     this.anim = TimelineBuilder.create().keyFrames(new KeyFrame(Duration.millis(300), new KeyValue(this.vbox_slideview.layoutYProperty(), 0))).build();
                     this.anim.statusProperty().addListener(new ChangeListener<Status>() {
                          @Override
                          public void changed(ObservableValue<? extends Status> observableValue, Status oldValue, Status newValue)
                          {
                               if (newValue == Status.STOPPED)
                               {
                                    setBlocked(false);
                               }
                          }
                     });
                     this.anim.play();
                     return;
                }
      
                setBlocked(false);
           }
      You will notice that I put the animation in a class variable. This is because I tried several other ways of blocking the animation, suggested by posts in this question: http://stackoverflow.com/questions/12663730/how-to-find-out-if-a-transition-is-already-running-on-a-node All of those methods provide the same result.

      I also introduce a listener for Y property of the sliding menu in `initialize()`:
           vbox_slideview.layoutYProperty().addListener(new ChangeListener<Number>() {
                @Override
                public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2)
                {
                     System.out.println("y = " + arg0.getValue().intValue());
                }
           });
      The results are weird (emphasis mine):
         (Status == RUNNING)
          y = -325
          y = -308
          y = -287
          y = -269
          y = -326 <<
          y = -218
          y = -326 <<
          y = -196
          y = -178
          y = -326 <<
          y = -158
          y = -326 <<
          y = -132
          y = -120
          y = -326 <<
          y = -96
          y = -326 <<
          y = -76
          y = -60
          y = -326 <<
          y = -28
          y = -326 <<
          y = -2
          y = 0
          (Status == STOPPED)
      The `layoutY` property is randomly set to the starting value during the animation. Moreover, when the mouse cursor is over the menu, the `onMouseMove` should not fire because it is only a handler for the `WebView`. However, after the animation completes and I move the cursor below the top 3% of the window, the `vbox_slideview` disappears and its `layoutY` is immediately set to `-326`. This should not happen when over `vbox_slideview`.

      Why is `vbox_slideview` being reset to its starting position constantly?

      Update:
      If I move the cursor pretty quickly over the top 3% so that it doesnt linger there the animation runs fine. This makes me think the events are processed in parallel rather than sequentially, and even though the first one blocks subsequent events from processing, there are already a bunch of others that are being processed.

      Edited by: 989524 on Feb 21, 2013 7:35 AM
        • 1. Re: Slide animation flicker
          James_D
          I can't quite see why your code isn't behaving. This works, though. I did the layout very slightly differently (and did it all in Java, but it's easy enough to extract the layout into FXML if you prefer).
          import javafx.animation.KeyFrame;
          import javafx.animation.KeyValue;
          import javafx.animation.Timeline;
          import javafx.application.Application;
          import javafx.beans.property.BooleanProperty;
          import javafx.beans.property.SimpleBooleanProperty;
          import javafx.beans.value.ChangeListener;
          import javafx.beans.value.ObservableValue;
          import javafx.event.ActionEvent;
          import javafx.event.EventHandler;
          import javafx.scene.Scene;
          import javafx.scene.input.MouseEvent;
          import javafx.scene.layout.Pane;
          import javafx.scene.layout.VBox;
          import javafx.scene.paint.Color;
          import javafx.scene.shape.Rectangle;
          import javafx.scene.web.WebView;
          import javafx.stage.Stage;
          import javafx.util.Duration;
          
          public class SlideMenuTest extends Application {
          
            @Override
            public void start(Stage primaryStage) {
              final Pane root = new Pane();
              final WebView webView = new WebView();
              webView.setMinWidth(1024);
              webView.setMinHeight(768);
              webView.setMaxWidth(1024);
              webView.setMaxHeight(768);
              webView.getEngine().load("http://forums.oracle.com/forums/messageview.jspa?messageID=10865685&stqc=true");
          
              final VBox slidingMenu = new VBox();
              slidingMenu.setMinWidth(1024);
              slidingMenu.setMaxWidth(1024);
              slidingMenu.setMinHeight(326);
              slidingMenu.setMaxHeight(326);
              slidingMenu.getChildren().add(new Rectangle(1024, 326, Color.GRAY.deriveColor(0, 1, 1, 0.8)));
          
              root.getChildren().addAll(webView, slidingMenu);
          
              slidingMenu.setLayoutY(-326);
          
              final BooleanProperty blockShowMenu = new SimpleBooleanProperty(false);
          
              webView.setOnMouseMoved(new EventHandler<MouseEvent>() {
          
                @Override
                public void handle(MouseEvent event) {
                  if (event.getSceneY() < 0.03 * root.getHeight() && !blockShowMenu.get()) {
                    blockShowMenu.set(true);
                    final Timeline showAnim = new Timeline(new KeyFrame(Duration.millis(300), new KeyValue(slidingMenu.layoutYProperty(), 0)));
                    showAnim.setOnFinished(new EventHandler<ActionEvent>() {
                      @Override
                      public void handle(ActionEvent event) {
                        blockShowMenu.set(false);
                      }
                    });
                    showAnim.play();
                  }
                }
              });
          
              slidingMenu.setOnMouseExited(new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent event) {
                  if (!blockShowMenu.get()) {
                    blockShowMenu.set(true);
                    final Timeline hideAnim = new Timeline(new KeyFrame(Duration.millis(300), new KeyValue(slidingMenu.layoutYProperty(), -326)));
                    hideAnim.setOnFinished(new EventHandler<ActionEvent>() {
                      @Override
                      public void handle(ActionEvent event) {
                        blockShowMenu.set(false);
                      }
                    });
                    hideAnim.play();
                  }
                }
              });
          
              slidingMenu.layoutYProperty().addListener(new ChangeListener<Number>() {
          
                @Override
                public void changed(ObservableValue<? extends Number> observable,
                    Number oldValue, Number newValue) {
                  System.out.printf(" y = %.1f%n", newValue);
                }
          
              });
          
              Scene scene = new Scene(root);
              primaryStage.setScene(scene);
              primaryStage.show();
            }
          
            public static void main(String[] args) {
              launch(args);
            }
          }