Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Wednesday, August 23, 2017

Setting the Pivot Point in a JavaFX ScaleTransition

This article shows how to use a JavaFX ScaleTransition to expand a container of controls from nothing to its full dimensions.  While this is especially easy to do if you are content to scale the container outward from the center, an extra transformation is needed to expand the container from the top left, in this case next to the originating control.

When working with animations, I like to use the transformations as much as possible over object properties like width and height.  That helps to preserve the integrity of the layout.  For example, a zero-width Node might cause other items to shift undesirably.  If keeping unaffected Nodes in their original place is important, prefer ScaleTransition to animating width and height and prefer TranslateTransition to x and y.

This video shows a button with a greater than sign.  Upon mouse enter, a panel to the right will appear from the top left and grow to its full dimensions, toward the bottom right.  This panel contains five TextFields.


A ScaleTransition is used to take the TextField VBox from a size of width=0 / height=0 to its full height on a mouse enter on the > Button.  When a mouse exit is generated from the now-showing TextField VBox, the process is reversed.

If the program used nothing but the ScaleTransition, it would appear that the TextField VBox was growing from the center outward.  This isn't the effect I wanted so I employed a secondary transformation to modify the "pivot point" or the focal point of the ScaleTransition.  This pivot point shifts the center to the top left, moving it along with the ScaleTransition.

This secondary animation is a TranslateTransition.  The TranslateTransition centers the TextField VBox on the top left point of its place in the layout.  Then, as the animation progress, the TextField VBox is shifted in place.

A ParallelTransition coordinates the two transformations (ScaleTransition and TranslateTransition).  This starts the transformations at the same time and they end together since they have the same Duration.

Code


The following is the code demonstrated in the video.

public class SlideAppExpand extends Application {

    private final static int APP_WIDTH = 800;
    private final static int APP_HEIGHT = 600;

    private VBox textsPanel;
    private double tpWidth = 0.0d;
    private double tpHeight = 0.0;

    @Override
    public void start(Stage primaryStage) throws Exception {

        VBox mainContainer = new VBox();
        mainContainer.setAlignment( Pos.CENTER );
        mainContainer.setSpacing(100.0);
        mainContainer.setPadding( new Insets(10.0d) );

        Label label = new Label("Select Input Unit");
        Button button = new Button(">");
        button.setOnMouseEntered( this::expandInOut );

        HBox labelButtonHBox = new HBox(label,button);
        labelButtonHBox.setSpacing(4.0);

        TextField tf1 = new TextField("Text 1");
        TextField tf2 = new TextField("Text 2");
        TextField tf3 = new TextField("Text 3");
        TextField tf4 = new TextField("Text 4");
        TextField tf5 = new TextField("Text 5");

        textsPanel = new VBox( tf1, tf2, tf3, tf4, tf5 );
        textsPanel.setSpacing( 4.0 );
        textsPanel.setPadding( new Insets(0,0,0,10) );

        textsPanel.setScaleX(0.0);
        textsPanel.setScaleY(0.0);

        textsPanel.setOnMouseExited( this::expandInOut );

        HBox hbox = new HBox(labelButtonHBox, textsPanel);
        hbox.setSpacing( 4.0 );

        mainContainer.getChildren().add( hbox );

        Scene scene = new Scene( mainContainer );

        primaryStage.setWidth( APP_WIDTH );
        primaryStage.setHeight( APP_HEIGHT );
        primaryStage.setScene( scene );
        primaryStage.setOnShown( (evt) -> {
            tpWidth = textsPanel.getWidth();
            tpHeight = textsPanel.getHeight();

            textsPanel.setTranslateX( -1 * tpWidth / 2 );
            textsPanel.setTranslateY( -1 * tpHeight / 2 );
        });
        primaryStage.show();
    }

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

    private void expandInOut(MouseEvent evt) {

        boolean expand = evt.getEventType().equals(
           MouseEvent.MOUSE_ENTERED);

        TranslateTransition tt = 
             new TranslateTransition(Duration.millis(750), textsPanel);
        tt.setToX( (expand)?0.0:(-1 * tpWidth / 2));
        tt.setToY( (expand)?0.0:(-1 * tpHeight/2 ));

        ScaleTransition st = new ScaleTransition(Duration.millis(750), 
             textsPanel);
        st.setToX( (expand)?1.0:0.0 );
        st.setToY( (expand)?1.0:0.0 );

        ParallelTransition pt = new ParallelTransition(tt, st);
        pt.play();
    }
}

While the JavaFX documentation alludes to a "pivot point", there is no call that can adjust the scaling position.  I would have liked to see a setPivot(TOP_LEFT) in the API.  With a few extra lines of code, you can achieve a similar effect.

No comments:

Post a Comment