FlexBox Layout mit JavaFX (2)

In der Webentwicklung haben sich Grid und FlexBox Layout inzwischen als die beliebtesten Algorihthmen für flexible, responsive Layouts durchgesetzt. In JavaFX gibt es ebenfalls eine mächtige GridView, ein FlexBox Layout gab es jedoch bislang leider noch nicht. Hier stellen wir unsere eigene FlexBoxPane vor, mit dem sich nun auch in JavaFX leichter responsive Anwendungen umsetzen lassen.

Im ersten Teil dieser Serie ging es vor allem um die Constraints, die auf der FlexBoxPane selbst gesetzt werden können. In diesem Teil geht es um die Constraints für die Children (oder FlexItems). Im dritten Teil sehen wir uns dann einige Beispiellayouts an, die mit der FlexBoxPane umgesetzt werden können.

Mit dem Aufruf des Videos erklärst Du Dich einverstanden, dass Deine Daten an YouTube übermittelt werden und das Du die Datenschutzerklärung gelesen hast.

In unserem Layouts Repository findet Ihr die im Video verwendete Demoanwendung mit der Ihr die Auswirkungen der einzelnen Constraints ausprobieren könnt.

Child Constraints

Bei JavaFX Komponenten werden Layout-Constraints einer Child-Komponente üblicherweise über eine statische Methode der Layout-Pane gesetzt. Das ist auch bei der FlexBoxPane nicht anders.

Order

Standardmäßig werden Flex-Items in der Reihenfolge des Hinzufügens angeordnet. Mit dem Order-Constraint kann man die Reihenfolge steuern, in der sie im Flex-Container erscheinen.

public class MainApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        FlexBoxPane flex = new FlexBoxPane();
        flex.getChildren().add(createLabel(1, 3));
        flex.getChildren().add(createLabel(2, 1));
        flex.getChildren().add(createLabel(3, 2));
        stage.setTitle("JavaFX FlexBox");
        Scene scene = new Scene(flex, 500, 200);
        scene.getStylesheets().add("/styles/Styles.css");
        stage.setScene(scene);
        stage.show();
    }

    private Label createLabel(int idx, int order) {
        Label label1 = new Label("Flex Item " + idx);
        label1.setPrefWidth(175);
        label1.setAlignment(Pos.CENTER);
        FlexBoxPane.setOrder(label1,order);
        FlexBoxPane.setMargin(label1, new Insets(5));
        return label1;
    }

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

}

Über den Order-Constraint kann man die Anordnung der Flex Items verändern

Flex Grow

FlexGrow definiert, wie stark ein Flex-Item bei Bedarf im Verhältnis zu anderen Items wachsen kann. FlexGrow ist ein einheitsloser Integer Wert, der als Verhältnis dient. Er gibt an, wie viel Platz im Flex-Container das Item einnehmen soll.

Wenn für alle Flex Items Flex-Grow auf 1 eingestellt ist, wird der verbleibende Platz im Container gleichmäßig auf alle Kinder verteilt.

Wenn eines der Kinder den Wert 2 hat, würde dieses Item doppelt so viel Platz einnehmen wie die anderen (oder es zumindest versuchen).

Zur Demonstration ändern wir den Beispielcode, sodass der 2. Label den übrigen Platz anfordert:

    @Override
    public void start(Stage stage) throws Exception {
        FlexBoxPane flex = new FlexBoxPane();
        flex.getChildren().add(createLabel(1, 1));
        flex.getChildren().add(createLabel(2, 2));
        flex.getChildren().add(createLabel(3, 1));
        stage.setTitle("JavaFX FlexBox");
        Scene scene = new Scene(flex, 500, 200);
        scene.getStylesheets().add("/styles/Styles.css");
        stage.setScene(scene);
        stage.show();
    }

    private Label createLabel(int idx, int flexGrow) {
        Label label1 = new Label("Flex Item " + idx);
        label1.setPrefWidth(55);
        label1.setAlignment(Pos.CENTER);
        label1.setMaxWidth(Double.MAX_VALUE);
        FlexBoxPane.setGrow(label1, flexGrow);
        FlexBoxPane.setMargin(label1, new Insets(5));
        return label1;
    }

Damit die Labels den Platz anfordern, ist es wichtig ihre maxWidth zu setzen. Ansonsten werden sie den übrigen Platz nicht beanspruchen.

Das FlexGrow-Constraint legt fest, wieviel Platz ein Item beansprucht.

Flex Shrink

Mit Flex Shrink legt man Analog zu "Grow" das Schrumpfungsverhältnis der Items untereinander fest.

FlexBasisPercent

Mit FlexbasisPercent kann man festlegen wieviel Platz ein Flex Item in Hauptrichtung prozentual von der Länge der Hauptachse einnehmen soll. Laut Spezifikation soll dieser Wert nicht verwendet werden, wenn in dieser Richtung bereits eine fixe Länge gesetzt ist. Leider lässt sich das in JavaFX nicht feststellen. Auch wenn man auf einem Node weder Minimum, noch Maximum, noch die bevozugte Größe gesetzt hat, hat dieser meist eine bevorzugte Größe, die ungleich 0 ist. Somit muss aber der FlexBasisPercent Wert laut Spezifikation ignoriert werden.

In Versionen <0.6 hat FlexBasisPercent daher keine Auswirkung, wenn prefWidth(-1) (oder prefHeight(-1) bei FlexDirection.COLUMN) etwas anderes als 0 zurückgibt. In Versionen ab 0.6 wird das anders gelöst:

Nur wenn bevorzugte, minimale und maximale Größe in einer Richtung gleich sind, wird davon ausgegegangen, dass die Größe in dieser Richtung gesetzt wurde, da dies der einzige Weg ist, um in JavaFX eine fixe Größe zu setzen. Andernfalls darf der FlexBasisPercent-Wert angewendet werden. Das folgende CodeBeispiel hat daher erst ab Version 0.6 den Effekt, den man im Screenshot sieht:

    @Override
    public void start(Stage stage) throws Exception {
        FlexBoxPane flex = new FlexBoxPane();
        flex.getChildren().add(createLabel(1, 8f));
        flex.getChildren().add(createLabel(2, 39f));
        flex.getChildren().add(createLabel(3, 49f));
        stage.setTitle("JavaFX FlexBox");
        Scene scene = new Scene(flex, 500, 200);
        scene.getStylesheets().add("/styles/Styles.css");
        stage.setScene(scene);
        stage.show();
    }

    private Label createLabel(int idx, float flexGrow) {
        Label label1 = new Label("Flex Item " + idx);
        label1.setMaxWidth(Double.MAX_VALUE);
        label1.setAlignment(Pos.CENTER);
        FlexBoxPane.setFlexBasisPercent(label1, flexGrow);
        FlexBoxPane.setMargin(label1, new Insets(5));
        return label1;
    }

Das FlexGrow-Constraint legt fest, wieviel Platz ein Item beansprucht.

AlignSelf

Mit AlignSelf kann die standardmäßige Ausrichtung für einzelne Flex-Items überschrieben werden. Im folgenden Beispiel ist auf der Komponente AlignItems.FLEX_END gesetzt. Label Nummer 2 überschreibt dies jedoch mit AlignSelf.FLEX_START.

    @Override
    public void start(Stage stage) throws Exception {
        FlexBoxPane flex = new FlexBoxPane();
        flex.setAlignItems(FlexboxLayout.AlignItems.FLEX_END);
        flex.getChildren().add(createLabel(1));
        Label label2 = createLabel(2);
        FlexBoxPane.setFlexAlignSelf(label2,FlexboxLayout.FlexItem.AlignSelf.FLEX_START);
        flex.getChildren().add(label2);
        flex.getChildren().add(createLabel(3));
        flex.getChildren().add(createLabel(4));
        flex.getChildren().add(createLabel(5));
        flex.getChildren().add(createLabel(6));
        stage.setTitle("JavaFX FlexBox");
        Scene scene = new Scene(flex, 500, 200);
        scene.getStylesheets().add("/styles/Styles.css");
        stage.setScene(scene);
        stage.show();
    }

    private Label createLabel(int idx) {
        Label label1 = new Label("Flex Item " + idx);
        label1.setPrefHeight(20+(idx*10));
        label1.setPrefWidth(100);
        label1.setMaxWidth(Double.MAX_VALUE);
        FlexBoxPane.setMargin(label1, new Insets(5));
        return label1;
    }

Das FlexGrow-Constraint legt fest, wieviel Platz ein Item beansprucht.

Zusammenfassung

Mit der FlexBoxPane steht nun auch in JavaFX eine Implementierung des Flex Box Layout Modells zur Verfügung. Damit werden sehr einfach elastische "Mobile first" Layouts möglich. Im ersten Teil des Tutorials ging es um die Parent Constraints. In diesem Teil haben wir uns die Constraints der Children angesehen, bevor wir im dritten Teil einige Beispiellayouts umsetzen werden. Die FlexBoxPane ist kostenlos verwendbar unter der GPLv2 mit Classpath Exception.

Resourcen und thematisch passende Trainings

JFXFlexBox Projekt

FlexBox für RoboVM

FlexBoxPane Demo Anwendung

JavaFX Performance Tuning

JavaFX für Einsteiger

JavaFX für Business Applikationen