Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX: how to create slide in animation effect for a pane (inside a transparent stage)

Tags:

javafx

I would like some guidelines on how to implement a slide in transition for a pane when user presses a button, just like Material Design does it for sliding menus.

This is a video link that illustrates my need.

I tried ScaleTransition, TranslateTransition, but they didn't do the trick.

The way I'm trying to implement it is not efficient.

// swipeMenuPane is builded in SceneBuilder and it is hidden,
// opacity = 0.0 and setX() = -getPrefWidth();
@FXML AnchorPane swipeMenuPane;
@FXML Button menuButton;

menuButton.setOnMouseClicked(e-> {
    swipeMenuPane.setOpacity(1.0);
    swipeTransition.play()
});

TranslateTransition swipeTransition = new TranslateTransition();
swipeTransition.setNode(swipeMenuPane);
swipeTransition.setDuration(Duration.millis(500));
swipeTransition.setToX(swipeMenuPane.getPrefWidth());

--- UPDATE ---

Here is the sample Gluon Application downloaded from here. It's a gradle project and I modified it to display a button instead of the default label.

I want to shrink the AnchorPane when user clicks the button.

What am I missing?

package com.helloworld;

import com.gluonhq.charm.glisten.animation.ShrinkExpandAnimation;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloWorld extends Application {

    ShrinkExpandAnimation anim;

    @Override
    public void start(Stage stage) {        
        Button btn = new Button("Click Me!");
        btn.setOnMouseClicked(e-> {
            System.out.println("swiping...");
            anim.play();
        });

        AnchorPane pane = new AnchorPane();
        pane.setStyle("-fx-background-color: coral");
        pane.getChildren().add(btn);

        // false to shrink or true to expand
        anim = new ShrinkExpandAnimation(pane, false);

        Scene scene = new Scene(new StackPane(pane), 640, 480);

        stage.setScene(scene);
        stage.show();
    }
}

--- UPDATE 2 ---

I managed to implement something similar to what I want using native JavaFX API and no external libraries.

Although, I ran into some issues.

  1. Shrinking an AnchorPane does NOT shrink/move ANY of its children nodes, because they stay in their layout positions.
  2. Shrinking any other Pane except AnchorPane DOES shrink/move its children nodes except from ImageView nodes.

The next two images illustrate the 1st issue I ran into.

This is an AnchorPane (with coral color at its full width; expanded) inside an AnchorPane (root pane with grey color).

expanded

And this is what happens when you click Menu button to shrink/hide it. As you can see the coral-colored pane got shrinked/hidden, but NOT its nodes (Label, ImageView)

collapsed

I post the whole code to reproduce the issue yourself:

public class SwipeMenuDemo extends Application {

    AnchorPane swapPane;
    Button btnMenu;
    boolean isExpanded = true;

    @Override
    public void start(Stage stage) {    
        Label swapPaneLabel = new Label("Expandable Pane");
        swapPaneLabel.setMinWidth(0);

        ImageView swapPaneImage = new ImageView("http://vignette1.wikia.nocookie.net/jfx/images/5/5a/JavaFXIsland600x300.png");
        swapPaneImage.setLayoutY(100);

        Label rootPaneLabel = new Label("Root Pane");
        rootPaneLabel.setStyle("-fx-font-size: 60;");
        rootPaneLabel.setLayoutX(180);
        rootPaneLabel.setLayoutY(180);

        swapPane = new AnchorPane();
        swapPane.setPrefSize(640, 440);
        swapPane.setMinWidth(0);
        swapPane.setLayoutY(40);
        swapPane.setStyle("-fx-background-color: coral; -fx-font-size: 52;");
        swapPane.getChildren().addAll(swapPaneImage, swapPaneLabel);

        btnMenu = new Button("Menu");
        btnMenu.setLayoutX(5);
        btnMenu.setLayoutY(5);
        btnMenu.setOnMouseClicked(e -> {
            if (isExpanded) hideSwapPane().play();
            else showSwapPane().play();
        });

        Button btnClose = new Button("Close");
        btnClose.setLayoutX(590);
        btnClose.setLayoutY(5);
        btnClose.setOnMouseClicked(e -> Platform.exit());

        AnchorPane rootPane = new AnchorPane();
        rootPane.setStyle("-fx-background-color: grey;");
        rootPane.getChildren().addAll(btnMenu, btnClose, rootPaneLabel, swapPane);

        Scene scene = new Scene(rootPane, 640, 480);

        stage.setScene(scene);
        stage.initStyle(StageStyle.UNDECORATED);
        stage.show();
    }

    private Animation hideSwapPane() {            
        btnMenu.setMouseTransparent(true);

        Animation collapsePanel = new Transition() {
            {
                setCycleDuration(Duration.millis(2500));
            }

            @Override
            protected void interpolate(double fraction) {
                swapPane.setPrefWidth(640 * (1.0 - fraction));
            }
        };

        collapsePanel.setOnFinished(e-> {
            isExpanded = false;
            btnMenu.setMouseTransparent(false);
        });

        return collapsePanel;
    }

    private Animation showSwapPane() {            
        btnMenu.setMouseTransparent(true);

        final Animation expandPanel = new Transition() {
            {
                setCycleDuration(Duration.millis(2500));
            }

            @Override
            protected void interpolate(double fraction) {
                swapPane.setPrefWidth(640 * fraction);
            }
        };

        expandPanel.setOnFinished(e-> {
            isExpanded = true;
            btnMenu.setMouseTransparent(false);
        });

        return expandPanel;
    }
}

--- UPDATE 3 ---

I modified the code Felipe Guizar Diaz provide me, according to my needs, since I want a dropshadow effect on my transparent stage window.

When I click the menu button to show the left pane it shows up in front of the shadow. Even though in SceneBuilder I've placed the StackPane with the dropshadow effect in front of all nodes.

This is the "artifact" when I press to show the menu and starts playing the open transition...

How can I fix it?

shadow artifact

like image 722
John Astralidis Avatar asked Jul 24 '15 03:07

John Astralidis


2 Answers

I am the author of the example video. I'll repeat the response that I did in the video comments: "you should think of it as a navigation drawer in android, the navigation drawer in JavaFX would be an AnchorPane with 2 children, first a StackPane that is equivalent to a FrameLayout working as our main content, where transitions of pane are made depending of the chosen item from the left side menu, and ultimately a ListView as our left side menu with a negative translateX that equals to the Listview width. Then when the user presses a button you must play an animation that sest the value of translateX to 0." You shouldn't use prefWidth() in the interpolate method of the two animations (collapse Panel, expand Pane), because the children don't resize, the margin arrangement is the only constraint that the AnchorPane has.

Check out this example that I did.

https://github.com/marconideveloper/leftsidemenuexample

public class FXMLDocumentController implements Initializable {

    @FXML
    private Button menu;
    @FXML
    private AnchorPane navList;
    @Override
    public void initialize(URL url, ResourceBundle rb) {
    //navList.setItems(FXCollections.observableArrayList("Red","Yellow","Blue"));
        prepareSlideMenuAnimation();
    }    

    private void prepareSlideMenuAnimation() {
        TranslateTransition openNav=new TranslateTransition(new Duration(350), navList);
        openNav.setToX(0);
        TranslateTransition closeNav=new TranslateTransition(new Duration(350), navList);
        menu.setOnAction((ActionEvent evt)->{
            if(navList.getTranslateX()!=0){
                openNav.play();
            }else{
                closeNav.setToX(-(navList.getWidth()));
                closeNav.play();
            }
        });
    }
}

Here is the fxml:

<AnchorPane xmlns:fx="http://javafx.com/fxml/1" id="AnchorPane" prefWidth="500" prefHeight="500"    fx:controller="leftslidemenusample.FXMLDocumentController">
    <children>

        <ToolBar AnchorPane.topAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" minHeight="56.0"   >
            <Button text="menu" fx:id="menu"  /> 
        </ToolBar>
        <StackPane fx:id="mainContent"  style="-fx-background-color:rgba(0,0,0,0.30)" AnchorPane.bottomAnchor="0.0" AnchorPane.topAnchor="56.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"    >
            <children>

            </children>
        </StackPane>
        <AnchorPane fx:id="navList" style="-fx-background-color:white" AnchorPane.topAnchor="56.0" AnchorPane.bottomAnchor="0.0" prefWidth="180.0" translateX="-180"   >
            <children>
                <Label text="left side menu"/>
            </children>
        </AnchorPane>

    </children>

</AnchorPane>
like image 145
Felipe Guizar Diaz Avatar answered Nov 05 '22 08:11

Felipe Guizar Diaz


Finally, I get it done.

They key features are:

  1. Set the shadow effect on the root pane using a custom pane that drows a shadow outside its layout bounds and crops its inside content, so it has a transparent content.
  2. The root pane can be anything else than AnchorPane.
  3. Clip the pane that holds the main content to its inside bounds.

Below is a snippet of the source code that controls these effects:

@Override
public void initialize(URL url, ResourceBundle rb) {  
    ...

    Rectangle clip = new Rectangle(rootPaneWidth, rootPaneHeight);
    rootPane.setClip(clip);
    rootPane.getChildren().add(setupShadowPane());
}

private Pane setupShadowPane() {
    Pane shadowPane = new Pane();
    shadowPane.setStyle(
        "-fx-background-color: white;" +
        "-fx-effect: dropshadow(gaussian, black, " + shadowSize + ", 0, 0, 0);" +
        "-fx-background-insets: " + shadowSize + ";"
    );

    Rectangle innerBounds = new Rectangle();
    Rectangle outerBounds = new Rectangle();
    shadowPane.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
        innerBounds.relocate(newBounds.getMinX() + shadowSize, newBounds.getMinY() + shadowSize);
        innerBounds.setWidth(newBounds.getWidth() - shadowSize * 2);
        innerBounds.setHeight(newBounds.getHeight() - shadowSize * 2);
        outerBounds.setWidth(newBounds.getWidth());
        outerBounds.setHeight(newBounds.getHeight());

        Shape clip = Shape.subtract(outerBounds, innerBounds);
        shadowPane.setClip(clip);
    });

    return shadowPane;
}

Slide Menu semi opened

semi-opened

Slide Menu fully opened

fully-opened

Slide Menu closed

closed

like image 30
John Astralidis Avatar answered Nov 05 '22 08:11

John Astralidis