节点处于活动状态时如何停止隐藏?

时间:2019-06-22 18:42:04

标签: javafx fxml

我引用的代码:Show / Hide a Node within a stage

遵循上一个有关自动隐藏的问题和答案:How to show / hide / auto hide a node

感谢c0der解决了上一个问题。

它有一个问题,好像它处于活动状态(如移动光标或单击)一样,Vbox节点仍会自动隐藏。如何在移动光标或发生某些事件时使Vbox节点保持可见而不隐藏?

当前行为是,如果我移动光标或单击VBox节点上的某些内容,则5秒钟后它仍会隐藏。

Testinggg.java:

package testinggg;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Testinggg extends Application {

private TestController controller;
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Test.fxml"));
        Parent root = loader.load();
        controller = loader.getController();
        stage.setScene(new Scene(root));
        stage.setFullScreen(true);
        stage.show();
    }

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

TestController.java:

package testinggg;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.VBox;
import javafx.util.Duration;

public class TestController implements Initializable {

    @FXML private VBox statusContainer;
    private TranslateTransition showStatus;
    private TranslateTransition hideStatus;
    private boolean showsStatus = false;
    private static final int AUTO_HIDE_DELAY = 5;  

    public void toggleStatus() {
        if( showsStatus ) {
            hide();
        }
        else {
            show();
        }
    }
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        showStatus = new TranslateTransition(Duration.millis(250), statusContainer);
        showStatus.setByY(-1080.0);
        showStatus.setOnFinished(event -> {
            showsStatus = true;
            autoHide();
        });

        hideStatus = new TranslateTransition(Duration.millis(250), statusContainer);
        hideStatus.setByY(1080.0);
        hideStatus.setOnFinished(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent event) {
                showsStatus = false;
            }
        });
    }  

    private void show(){
        hideStatus.stop();
        showStatus.play();
    }

    private void hide(){
        showStatus.stop();
        hideStatus.play();
    }

    private void autoHide(){
        Duration duration = Duration.seconds(AUTO_HIDE_DELAY);
        PauseTransition transition = new PauseTransition(duration);
        transition.setOnFinished(evt ->{
            if( showsStatus ) {
                hide();
            }
        });
        transition.play();
    }
}

Test.fxml:

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

<?import javafx.scene.text.*?>
<?import javafx.scene.image.*?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="testinggg.TestController">
   <children>
      <AnchorPane id="AnchorPane" maxHeight="-Infinity" prefHeight="1080.0" prefWidth="1920.0" StackPane.alignment="TOP_LEFT">
         <children>
            <Button mnemonicParsing="false" onAction="#toggleStatus" prefHeight="1080.0" prefWidth="1920.0" text="Button" />
         </children>
      </AnchorPane>
      <VBox fx:id="statusContainer" maxHeight="1080.0" prefHeight="1080.0" translateY="1080.0" StackPane.alignment="BOTTOM_LEFT">
         <children>
            <AnchorPane prefHeight="668.0" prefWidth="1266.0">
               <VBox.margin>
                  <Insets bottom="50.0" left="50.0" right="50.0" top="50.0" />
               </VBox.margin>
               <children>
                  <ImageView fitHeight="540.0" fitWidth="1820.0" layoutY="43.0" pickOnBounds="true">
                     <image>
                        <Image url="@../Rainbow%20Poro.png" />
                     </image>
                  </ImageView>
                  <ImageView fitHeight="44.0" fitWidth="153.0" layoutX="857.0" pickOnBounds="true" preserveRatio="true">
                     <image>
                        <Image url="@../logo.png" />
                     </image>
                  </ImageView>
                  <ScrollPane layoutY="582.0" prefHeight="391.0" prefViewportHeight="208.0" prefViewportWidth="1266.0" prefWidth="1820.0">
                     <content>
                        <TextArea editable="false" layoutY="460.0" prefHeight="515.0" prefWidth="1804.0" text="Sometexthere SometexthereSometexthereSometexthereSometexthereSometexthereSometexthereSometexthereSometexthereSometexthere&#10;Some&#10;Text&#10;Here&#10;Some&#10;Text &#10;Here&#10;Some&#10;Text &#10;Here&#10;Some&#10;Text &#10;Here&#10;Some&#10;Text &#10;Here&#10;Some&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;HereSome&#10;Text &#10;Here&#10;Some&#10;Text &#10;Here" />
                     </content>
                  </ScrollPane>
                  <Button layoutX="1775.0" mnemonicParsing="false" onAction="#toggleStatus" text="Close" />
               </children>
            </AnchorPane>
         </children>
      </VBox>
   </children>
   <stylesheets>
    <URL value="@test1.css" />
  </stylesheets>
</StackPane>

2 个答案:

答案 0 :(得分:2)

您可以保留对PauseTransition的引用,并为每个输入事件(或仅特定事件)调用playFromStart()。这是playFromStart()中的documentation

  

从初始位置向前播放Animation

     

等效于

animation.stop();
animation.setRate = setRate(Math.abs(animation.getRate()));
animation.jumpTo(Duration.ZERO);
animation.play();
     

注意:

     
      
  • playFromStart()是一个异步调用,Animation可能不会立即开始。
  •   

您将要在事件 filter 中执行此操作,因为子节点可能会占用事件,这意味着它们不会到达事件 handler 。不要使用过滤器中的事件,因为这将阻止子节点接收事件。

请注意,这种方法只会在用户在其中进行某些操作时阻止状态容器隐藏。如果鼠标只是悬停在状态容器上而不执行任何操作,则状态容器仍将自动隐藏。如果您不想这样做,请考虑使用@coder's approach;如果您想无限期地暂停自动隐藏计时器,直到用户将焦点从状态容器移开,则该方法更有意义。我的方法更适合于在自上次输入以来经过 X 时间后隐藏状态容器,无论如何。当然,如果找到合适的用例,则可以始终将两种方法结合起来。


这是一个小例子:

FXML:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>

<StackPane xmlns="http://javafx.com/javafx/12.0.1" xmlns:fx="http://javafx.com/fxml/1"
           fx:controller="com.example.Controller" prefWidth="600.0" prefHeight="400.0">

    <VBox fx:id="statusContainer" prefHeight="150" maxHeight="-Infinity" 
          style="-fx-background-color: firebrick;" StackPane.alignment="BOTTOM_CENTER"/>

    <Button fx:id="toggleStatusBtn" text="Hide Status" onAction="#handleToggleStatus"/>

</StackPane>

控制器:

package com.example;

import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.input.InputEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.util.Duration;

public class Controller {

    @FXML private Button toggleStatusBtn;
    @FXML private VBox statusContainer;

    private TranslateTransition showHideAnimation;
    private PauseTransition autoHideTimer;
    private boolean statusShowing = true;

    @FXML
    private void initialize() {
        showHideAnimation = new TranslateTransition(Duration.millis(250), statusContainer);
        showHideAnimation.setFromY(0);
        showHideAnimation.toYProperty().bind(statusContainer.prefHeightProperty());

        autoHideTimer = new PauseTransition(Duration.seconds(5));
        autoHideTimer.setOnFinished(event -> hideStatusContainer());

        statusContainer.addEventFilter(InputEvent.ANY, event -> {
            if (statusShowing) {
                autoHideTimer.playFromStart(); // restart timer
            } else if (event.getEventType() != MouseEvent.MOUSE_EXITED) {
                showStatusContainer(); // user did something while container was hiding
            }
        });
    }

    @FXML
    private void handleToggleStatus(ActionEvent event) {
        event.consume();
        if (statusShowing) {
            hideStatusContainer();
        } else {
            showStatusContainer();
        }
    }

    private void hideStatusContainer() {
        if (statusShowing) {
            statusShowing = false;
            showHideAnimation.setRate(1);
            showHideAnimation.play();
            toggleStatusBtn.setText("Show Status");
        }
    }

    private void showStatusContainer() {
        if (!statusShowing) {
            statusShowing = true;
            showHideAnimation.setRate(-1); // reverse animation
            showHideAnimation.play();
            autoHideTimer.playFromStart();
            toggleStatusBtn.setText("Hide Status");
        }
    }

}

注意:statusContainer最初显示,并且自动隐藏功能直到隐藏后再次显示才起作用。

注意:上面的显示和隐藏使用相同的TranslateTransition。通常,这可以使动画更流畅-尤其是在快速切换时。

答案 1 :(得分:2)

您可以将控件布尔值添加到控制器:

private boolean isStatusContainerBusy = false;

让感兴趣的事件切换它。例如,当鼠标进入时将其设置为true,而当鼠标退出时将其设置为false:

statusContainer.setOnMouseEntered(e-> isStatusContainerBusy = true);
statusContainer.setOnMouseExited(e-> isStatusContainerBusy = false);

并使用它来控制自动隐藏:

transition.setOnFinished(evt ->{
    if( showsStatus ) {
        if(isStatusContainerBusy) {
            transition.play(); //start auto hide transition again
        }else{
            hide();
        }
    }
});

将它们放在一起:

public class TestController {

    @FXML private VBox statusContainer;

    private TranslateTransition showStatus;
    private TranslateTransition hideStatus;
    private boolean showsStatus = false;
    private static final int AUTO_HIDE_DEALY = 5;
    private boolean isStatusContainerBusy = false;

    @FXML void initialize() {

        showStatus = new TranslateTransition(Duration.millis(250), statusContainer);
        showStatus.setByY(-100.0);
        showStatus.setOnFinished(event -> {
            showsStatus = true;
            autoHide();
        });
        hideStatus = new TranslateTransition(Duration.millis(250), statusContainer);
        hideStatus.setByY(100.0);
        hideStatus.setOnFinished(event -> showsStatus = false);
        statusContainer.setOnMouseEntered(e-> isStatusContainerBusy = true);
        statusContainer.setOnMouseExited(e-> isStatusContainerBusy = false);
    }

    public void toggleStatus() {
        if( showsStatus ) {
            hide();
        }
        else {
            show();
        }
    }

    private void show(){
        hideStatus.stop();
        showStatus.play();
    }

    private void hide(){
        showStatus.stop();
        hideStatus.play();
    }

    private void autoHide() {
        Duration duration = Duration.seconds(AUTO_HIDE_DEALY);
        PauseTransition transition = new PauseTransition(duration);
        transition.setOnFinished(evt ->{
            if( showsStatus ) {
                if(isStatusContainerBusy) {
                    transition.play(); //start auto hide transition again
                }else{
                    hide();
                }
            }
        });
        transition.play();
    }
}