Search code examples
animationjavafxtranslate-animationhbox

How to slide the node when added to an HBox in JavaFX?


I am creating a panel with an HBox that has 2 or 3 children. The first child is a VBox with icons and stays on the left side of the screen, when I hover the mouse over the VBox (1st child) I want to add my second child that is a VBox with buttons. My third child is an AnchorPane that supports my content.

My issue is, how do I add my second VBox to the HBox with a transition slide (left to right)?

The finality of hide my second child (VBox with buttons) was at increase my width content of third child (content AnchorPane);

Example Code

public class Test extends Application {

    @Override
    public void start(Stage primaryStage) {

        HBox root = new HBox();

        Scene scene = new Scene(root, 300, 250);
        VBox c1 = new VBox();
        ImageView i1 = new ImageView(new Image(getClass().getResourceAsStream("home/home.png")));
        ImageView i2 = new ImageView(new Image(getClass().getResourceAsStream("home/contactos.png")));
        ImageView i3 = new ImageView(new Image(getClass().getResourceAsStream("home/info.png")));
        c1.getChildren().addAll(i1, i2, i3);
        VBox c2 = new VBox();
        Button b1 = new Button("home opção1");
        Button b2 = new Button("home opção2");
        c2.getChildren().addAll(b1, b2);
        AnchorPane c3 = new AnchorPane();

        // Set backgrounds
        c1.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
        c2.setBackground(new Background(new BackgroundFill(Color.GRAY, CornerRadii.EMPTY, Insets.EMPTY)));
        c3.setBackground(new Background(new BackgroundFill(Color.rgb(255,255,148), CornerRadii.EMPTY, Insets.EMPTY)));
        root.getChildren().addAll(c1, c3);
        c1.setOnMouseEntered(new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {
                root.getChildren().add(1, c2);
                // Fault transation slide
            }
        });

        c1.setOnMouseExited(new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {
                root.getChildren().remove(1);
            }
        });

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}

Solution

  • Unfortunately there is no predefined approach for getting the sliding effect in JavaFX. You can get that effect by understanding/implementing the concept of clipping. Internally the same concept is used for ScrollPane and other controls where we need to clip the content.

    The idea is that, we clip the layout that we want to slide and change its value gradually by using a timeline. A more detailed explanation regarding this can be found in my blog. (I wrote this blog back in early 2012, so the code may look a bit obsolete ;) but you should get the concept)

    I quickly worked the below demo for your requirement. This should help you to get some idea.

    import javafx.animation.KeyFrame;
    import javafx.animation.KeyValue;
    import javafx.animation.Timeline;
    import javafx.application.Application;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.layout.*;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Circle;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.text.Text;
    import javafx.stage.Stage;
    import javafx.util.Duration;
    
    import java.util.stream.Stream;
    
    
    public class SlideMenuDemo extends Application {
        /* THE ONLY DRAWBACK OF THIS APPROACH IS YOU HAVE TO KNOW THE WIDTH OF THE SUBMENU AHEAD*/
        double subMenuWidth = 140;
    
        HBox root;
        VBox subMenu;
        VBox menuPane;
        StackPane subMenuPane;
    
        private Rectangle clipRect;
        private Timeline timelineHide;
        private Timeline timelineShow;
    
        private final Color[] colors = {Color.RED, Color.BLUE, Color.GREEN, Color.LAVENDER, Color.PINK};
        private final String[] shapes = {"cirlce", "triangle", "square", "rectangle"};
    
        @Override
        public void start(Stage primaryStage) {
            root = new HBox();
            Scene scene = new Scene(root, 400, 300);
            primaryStage.setTitle("Sliding Demo");
            primaryStage.setScene(scene);
            primaryStage.show();
    
            // Main menu pane
            menuPane = new VBox();
            menuPane.setStyle("-fx-background-color:#DDDDDD;");
            Stream.of(shapes).forEach(shape -> menuPane.getChildren().addAll(buildMenuButton(shape, Color.BLACK, null)));
    
            // Sub menu pane
            subMenu = new VBox();
            subMenuPane = new StackPane(subMenu);
            subMenuPane.setMinWidth(0);
            subMenuPane.setPrefWidth(0);
    
            StackPane subMenuContainer = new StackPane(subMenuPane);
            subMenuContainer.setStyle("-fx-background-color:#AAAAAA");
            HBox menuBox = new HBox(menuPane, subMenuContainer);
            menuBox.setOnMouseExited(e -> hideSubMenu());
    
            // Content Pane
            StackPane contentPane = new StackPane(new Text("Hello Slide Checking"));
            contentPane.setAlignment(Pos.TOP_LEFT);
            contentPane.setPadding(new Insets(15));
            HBox.setHgrow(contentPane, Priority.ALWAYS);
            contentPane.setStyle("-fx-background-color:#0000FF70,#FFFFFF;-fx-background-insets:0,1;");
    
            root.getChildren().addAll(menuBox, contentPane);
            setAnimation();
        }
    
        private void hideSubMenu() {
            timelineHide.play();
        }
    
        private void showSubMenu() {
            timelineShow.play();
        }
    
        private void setAnimation() {
            clipRect = new Rectangle();
            clipRect.setWidth(0);
            clipRect.heightProperty().bind(root.heightProperty());
            clipRect.translateXProperty().set(subMenuWidth);
            subMenuPane.setClip(clipRect);
            subMenuPane.translateXProperty().set(-subMenuWidth);
    
            /* Event handler hide is finished. */
            EventHandler<ActionEvent> onFinished = e -> {
                menuPane.getChildren().stream().forEach(n -> n.setStyle(null));
                subMenu.getChildren().clear();
            };
    
            timelineShow = new Timeline();
            timelineHide = new Timeline();
    
            /* Animation for show. */
            timelineShow.setCycleCount(1);
            final KeyValue kvDwn1a = new KeyValue(clipRect.widthProperty(), subMenuWidth);
            final KeyValue kvDwn1b = new KeyValue(subMenuPane.prefWidthProperty(), subMenuWidth);
            final KeyValue kvDwn1c = new KeyValue(subMenuPane.minWidthProperty(), subMenuWidth);
            final KeyValue kvDwn2 = new KeyValue(clipRect.translateXProperty(), 0);
            final KeyValue kvDwn3 = new KeyValue(subMenuPane.translateXProperty(), 0);
            final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), kvDwn1a, kvDwn1b, kvDwn1c, kvDwn2, kvDwn3);
            timelineShow.getKeyFrames().add(kfDwn);
    
            /* Animation for hide. */
            timelineHide.setCycleCount(1);
            final KeyValue kvUp1a = new KeyValue(clipRect.widthProperty(), 0);
            final KeyValue kvUp1b = new KeyValue(subMenuPane.prefWidthProperty(), 0);
            final KeyValue kvUp1c = new KeyValue(subMenuPane.minWidthProperty(), 0);
            final KeyValue kvUp2 = new KeyValue(clipRect.translateXProperty(), subMenuWidth);
            final KeyValue kvUp3 = new KeyValue(subMenuPane.translateXProperty(), -subMenuWidth);
            final KeyFrame kfUp = new KeyFrame(Duration.millis(200), onFinished, kvUp1a, kvUp1b, kvUp1c, kvUp2, kvUp3);
            timelineHide.getKeyFrames().add(kfUp);
        }
    
        private StackPane buildMenuButton(String type, Color color, String text) {
            double size = 50;
            double sSize = (size / 5) * 4;
            double hSize = sSize / 2;
            StackPane menuButton = new StackPane();
            menuButton.setPadding(new Insets(0, 5, 0, 5));
            menuButton.setMaxHeight(size);
            menuButton.setMinHeight(size);
    
            Node shape = null;
            switch (type) {
                case "triangle":
                    StackPane s = new StackPane();
                    s.setPrefSize(sSize, sSize);
                    s.setMaxSize(sSize, sSize);
                    s.setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)));
                    s.setStyle("-fx-shape:\"M0 1 L1 1 L.5 0 Z\";");
                    s.setPadding(new Insets(hSize));
                    shape = s;
                    break;
                case "square":
                    shape = new Rectangle(sSize, sSize, color);
                    break;
                case "rectangle":
                    shape = new Rectangle(sSize, hSize, color);
                    break;
                default:
                    shape = new Circle(hSize, color);
                    break;
            }
            HBox hb = new HBox(shape);
            hb.setAlignment(Pos.CENTER_LEFT);
            if (text != null) {
    
                hb.setSpacing(10);
                hb.getChildren().add(new Label(text));
            }
            menuButton.getChildren().add(hb);
    
            if (text == null) {
                // Main menu button
                menuButton.setOnMouseEntered(e -> {
                    menuPane.getChildren().stream().forEach(n -> n.setStyle(null));
                    subMenu.getChildren().clear();
                    menuButton.setStyle("-fx-background-color:#AAAAAA;");
                    Stream.of(colors).forEach(c -> subMenu.getChildren().addAll(buildMenuButton(type, c, c.toString())));
                    if (subMenuPane.getWidth() == 0) {
                        showSubMenu();
                    }
                });
            } else {
                // Sub menu button
                menuButton.setPrefWidth(subMenuWidth);
                menuButton.setMinWidth(subMenuWidth);
                menuButton.setOnMouseEntered(e -> menuButton.setStyle("-fx-background-color:#777777;"));
                menuButton.setOnMouseExited(e -> menuButton.setStyle(null));
            }
            return menuButton;
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }