简体   繁体   中英

JavaFX slide animation flicker

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: 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.

You showed us the "popup" part of the slider, the auto-hiding part is also needed for full examining. I suspect your auto-hiding part is continuously being triggered both while popup sliding and after finishing of it. Anyway, why to create timeline every time, use one instance of it only.
On my test case (Javafx version 2.2.0) with similar codes with yours, the slider is showing without flicker and stays on the screen with layoutY = 0 after the animation is finished:

Sample.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.web.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<StackPane id="StackPane" minHeight="568.0" minWidth="824.0" xmlns:fx="http://javafx.com/fxml" fx:controller="demo.SampleController">
  <children>
    <WebView fx:id="wv" onMouseMoved="#onMouseMove" prefHeight="568.0" prefWidth="824.0" />
    <VBox fx:id="vbox_slideview" fillWidth="true" maxHeight="326.0" maxWidth="824.0" minHeight="326.0" minWidth="824.0" prefHeight="326.0" prefWidth="824.0"
          visible="false" StackPane.alignment="TOP_CENTER" style="-fx-background-color: green">
      <StackPane.margin>
       <Insets top="-326.0" />
      </StackPane.margin>
    </VBox>
  </children>
</StackPane>

SampleController

public class SampleController implements Initializable {

    @FXML
    private VBox vbox_slideview;

    private Timeline timeline;

    @FXML
    public void onMouseMove(MouseEvent e) {
        if (e.getSceneY() < 0.03 * JavaFXApplication110.instance.stage.getScene().getHeight()) {
            this.vbox_slideview.setVisible(true);
            if (timeline.getStatus().equals(Animation.Status.STOPPED)) {
                timeline.play();
            }
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        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());
            }
        });

        timeline = TimelineBuilder.create().keyFrames(
                new KeyFrame(
                Duration.millis(300),
                new KeyValue(this.vbox_slideview.layoutYProperty(),
                0)))
                .build();
    }
}

不要使用LayoutX / Y()...使用TranslateX / Y()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM