JavaFX - Collapsing VBox

119 Views Asked by At

I have a VBox containing MFXToggleButtons. I want to collapse and expand this VBox in height so that it disappears completely. Having thus made a settings window that, when expanded, moves other objects on the form. But when you try to specify any height in the VBox, nothing changes. As I understand it, this is because the objects inside the VBox do not allow it to decrease.

My VBox

            <VBox>
                <MFXToggleButton selected="true" text="Installed" >
                    <VBox.margin>
                        <Insets top="20" bottom="5" left="10"  />
                    </VBox.margin>
                </MFXToggleButton>
                <MFXToggleButton selected="true" text="Releases">
                    <VBox.margin>
                        <Insets top="10" bottom="5" left="10" />
                    </VBox.margin>
                </MFXToggleButton>
                <MFXToggleButton selected="true" text="Modified">
                    <VBox.margin>
                        <Insets top="10" bottom="5" left="10" />
                    </VBox.margin>
                </MFXToggleButton>
                <MFXToggleButton selected="false" text="Snapshots">
                    <VBox.margin>
                        <Insets top="10" bottom="5" left="10" />
                    </VBox.margin>
                </MFXToggleButton>
                <MFXToggleButton selected="false" text="Olds">
                    <VBox.margin>
                        <Insets top="10" bottom="20" left="10" />
                    </VBox.margin>
                </MFXToggleButton>
            </VBox>

I have already tried to use other containers and also reduce the height of the MFXToggleButton themselves. But it didn't give me any results.

2

There are 2 best solutions below

0
James_D On

First note that the functionality you are describing is already implemented by TitledPane. Typically you should use built-in functionality, when it does what you need, instead of trying to implement it yourself.

If you want to write your own animation to collapse a pane, you can animate the value of the maxHeightProperty(). To collapse it, you should animate the max height from the current height to zero. To expand it again, compute the preferred height using prefHeight(width), and animate the max height from zero to the preferred height. At the end of the "expand" animation, you probably want to set the max height to Region.USE_PREF_HEIGHT, so that it can continue to respect its preferred height if the contents change.

A couple of implementation notes:

  1. The default minimum height of a VBox is non-zero (see docs). To allow it to shrink completely, you need to explicitly set the minHeight to zero.

  2. Not all containers clip the content of child nodes if they overflow the bounds allocated to them. So you probably need to explicitly provide a clip and bind it to the bounds of the content.

  3. You don't want the container for the expanding content to allocate extra space to it (because the algorithm described above assumes it is not taller than its preferred size). You can guarantee this (at least in cases where it's possible) by setting the maxHeight to Region.USE_PREF_SIZE.

Here is a quick example:

hello-view.fxml:

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

<?import io.github.palexdev.materialfx.controls.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.Rectangle?>
<VBox spacing="20.0" xmlns:fx="http://javafx.com/fxml"
      fx:controller="com.example.demo.HelloController">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
    </padding>
    <Button text="Expand/Collapse" onAction="#expand"/>
    <VBox fx:id="expandContent" minHeight="0">
        <maxHeight><Region fx:constant="USE_PREF_SIZE"/></maxHeight>
        <clip><Rectangle fx:id="expandClip"/></clip>
        <MFXToggleButton selected="true" text="Installed" >
            <VBox.margin>
                <Insets top="20" bottom="5" left="10"  />
            </VBox.margin>
        </MFXToggleButton>
        <MFXToggleButton selected="true" text="Releases">
            <VBox.margin>
                <Insets top="10" bottom="5" left="10" />
            </VBox.margin>
        </MFXToggleButton>
        <MFXToggleButton selected="true" text="Modified">
            <VBox.margin>
                <Insets top="10" bottom="5" left="10" />
            </VBox.margin>
        </MFXToggleButton>
        <MFXToggleButton selected="false" text="Snapshots">
            <VBox.margin>
                <Insets top="10" bottom="5" left="10" />
            </VBox.margin>
        </MFXToggleButton>
        <MFXToggleButton selected="false" text="Olds">
            <VBox.margin>
                <Insets top="10" bottom="20" left="10" />
            </VBox.margin>
        </MFXToggleButton>
    </VBox>
    <Label fx:id="welcomeText"/>
    <Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>

HelloController.java:

package com.example.demo;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;

public class HelloController {
    @FXML
    private Label welcomeText;

    @FXML
    private VBox expandContent;
    private boolean expanded = true;
    @FXML
    private Rectangle expandClip;

    @FXML
    private void initialize() {
        expandClip.widthProperty().bind(expandContent.widthProperty());
        expandClip.heightProperty().bind(expandContent.heightProperty());
    }

    @FXML
    protected void onHelloButtonClick() {
        welcomeText.setText("Welcome to JavaFX Application!");
    }

    @FXML
    private void expand() {
        if (expanded) {
            // animate collapse:
            double currentHeight = expandContent.getHeight();
            Timeline timeline = new Timeline(
                    new KeyFrame(Duration.ZERO, new KeyValue(expandContent.maxHeightProperty(), currentHeight)),
                    new KeyFrame(Duration.seconds(0.5), new KeyValue(expandContent.maxHeightProperty(), 0))
            );
            timeline.setOnFinished(e -> {
                expanded = false;
            });
            timeline.play();
        } else {
            // animate expand:

            // compute the preferred height of the content:
            double prefHeight = expandContent.prefHeight(expandContent.getWidth());
            // animate the max height from zero to the pref height:
            Timeline timeline = new Timeline(
                    new KeyFrame(Duration.ZERO, new KeyValue(expandContent.maxHeightProperty(), 0)),
                    new KeyFrame(Duration.seconds(0.5), new KeyValue(expandContent.maxHeightProperty(), prefHeight))
            );
            // when done, set the flag, and set the max height to the sentinel value
            // this latter part is necessary to allow the content to continue to respect
            // the pref height if the scene graph changes, etc.
            timeline.setOnFinished(e -> {
                expanded = true;
                expandContent.setMaxHeight(Region.USE_PREF_SIZE);
            });
            timeline.play();
        }

    }
}

HelloApplication.java:

package com.example.demo;

import io.github.palexdev.materialfx.css.themes.MFXThemeManager;
import io.github.palexdev.materialfx.css.themes.Themes;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load());
        MFXThemeManager.addOn(scene, Themes.DEFAULT, Themes.LEGACY);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

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

Expanded:

enter image description here

Collapsed:

enter image description here

0
0xLairon1 On

I figured out why my collapse animation lags so much. I think that this is due to the fact that during the animation the list with custom cells moves and lags occur at this moment. I will redo my design so that the list does not move)