ConstraintLayout in Compose

ConstraintLayout ist ein Layout, mit dem Sie Composables relativ zu anderen Composables auf dem Bildschirm platzieren können. Es ist eine Alternative zur Verwendung mehrerer verschachtelter Row, Column, Box, und anderer benutzerdefinierter Layout-Elemente.

Im View-System war ConstraintLayout die empfohlene Methode zum Erstellen großer und komplexer Layouts, da eine flache Ansichtshierarchie für die Leistung besser war als verschachtelte Ansichten. Dies ist jedoch in Compose kein Problem, da tiefe Layout-Hierarchien effizient verarbeitet werden können. Daher ist `ConstraintLayout` nicht so vorteilhaft.

Erste Schritte mit ConstraintLayout

Wenn Sie ConstraintLayout in Compose verwenden möchten, müssen Sie diese Abhängigkeit zusätzlich zur Compose-Einrichtung in build.gradle hinzufügen:

implementation "androidx.constraintlayout:constraintlayout-compose:$constraintlayout_compose_version"

ConstraintLayout in Compose funktioniert mit einer DSL so:

  • Erstellen Sie Referenzen für jedes Composable in der ConstraintLayout mit createRefs() oder createRefFor().
  • Einschränkungen werden mit dem Modifikator constrainAs() angegeben, der die Referenz als Parameter verwendet. Sie können die Einschränkungen im Body-Lambda angeben.
  • Einschränkungen werden mit linkTo() oder anderen hilfreichen Methoden angegeben.
  • parent ist eine vorhandene Referenz, mit der Einschränkungen für das ConstraintLayout-Composable selbst angegeben werden können.

Hier ist ein Beispiel für ein Composable mit ConstraintLayout:

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text(
            "Text",
            Modifier.constrainAs(text) {
                top.linkTo(button.bottom, margin = 16.dp)
            }
        )
    }
}

Dieser Code schränkt die Oberseite des Button mit einem Abstand von 16.dp auf das übergeordnete Element ein und den Text mit einem Abstand von 16.dp auf die Unterseite des Button.

Die Schaltfläche wird über dem Text angezeigt.
Abbildung 1. Ein Button und ein Text Composable, die in einem ConstraintLayout aneinander gebunden sind.

Entkoppelte API

Im ConstraintLayout-Beispiel werden Einschränkungen inline mit einem Modifikator im Composable angegeben, auf das sie angewendet werden. Es gibt jedoch Situationen, in denen es besser ist, die Einschränkungen von den Layouts zu entkoppeln, auf die sie angewendet werden. Sie können beispielsweise die Einschränkungen je nach Bildschirmkonfiguration ändern oder zwischen zwei Einschränkungssätzen animieren.

In solchen Fällen können Sie ConstraintLayout auf andere Weise verwenden:

  1. Übergeben Sie ein ConstraintSet als Parameter an ConstraintLayout.
  2. Weisen Sie Composables mit dem layoutId Modifikator Referenzen zu, die in der ConstraintSet erstellt wurden.

@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

Wenn Sie die Einschränkungen ändern müssen, können Sie einfach ein anderes ConstraintSet übergeben.

ConstraintLayout-Konzepte

ConstraintLayout enthält Konzepte wie Richtlinien, Barrieren und Ketten, die bei der Positionierung von Elementen in Ihrem Composable helfen können.

Richtlinien

Richtlinien sind kleine visuelle Hilfen zum Entwerfen von Layouts. Composables können auf eine Richtlinie beschränkt werden. Richtlinien sind nützlich, um Elemente an einer bestimmten dp oder percentage innerhalb des übergeordneten Composables zu positionieren.

Es gibt zwei verschiedene Arten von Richtlinien: vertikale und horizontale. Die beiden horizontalen sind top und bottom, die beiden vertikalen start und end.

ConstraintLayout {
    // Create guideline from the start of the parent at 10% the width of the Composable
    val startGuideline = createGuidelineFromStart(0.1f)
    // Create guideline from the end of the parent at 10% the width of the Composable
    val endGuideline = createGuidelineFromEnd(0.1f)
    //  Create guideline from 16 dp from the top of the parent
    val topGuideline = createGuidelineFromTop(16.dp)
    //  Create guideline from 16 dp from the bottom of the parent
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

Verwenden Sie createGuidelineFrom* mit dem erforderlichen Richtlinientyp, um eine Richtlinie zu erstellen. Dadurch wird eine Referenz erstellt, die im Block Modifier.constrainAs() verwendet werden kann.

Barrieren

Barrieren verweisen auf mehrere Composables, um eine virtuelle Richtlinie zu erstellen basierend auf dem extremsten Widget auf der angegebenen Seite.

Verwenden Sie createTopBarrier() (oder createBottomBarrier(), createEndBarrier(), createStartBarrier()) und geben Sie die Referenzen an, die die Barriere bilden sollen.

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val topBarrier = createTopBarrier(button, text)
    }
}

Die Barriere kann dann in einem Modifier.constrainAs()-Block verwendet werden.

Ketten

Ketten bieten ein gruppenähnliches Verhalten auf einer einzelnen Achse (horizontal oder vertikal). Die andere Achse kann unabhängig eingeschränkt werden.

Verwenden Sie createVerticalChain oder createHorizontalChain, um eine Kette zu erstellen:

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
        val horizontalChain = createHorizontalChain(button, text)
    }
}

Die Kette kann dann im Block Modifier.constrainAs() verwendet werden.

Eine Kette kann mit verschiedenen ChainStyles konfiguriert werden, die festlegen, wie mit dem Raum um ein Composable herum umgegangen werden soll, z. B.:

  • ChainStyle.Spread: Der Abstand wird gleichmäßig auf alle Composables verteilt, einschließlich des kostenlosen Raums vor dem ersten und nach dem letzten Composable.
  • ChainStyle.SpreadInside: Der Abstand wird gleichmäßig auf alle Composables verteilt, ohne kostenlosen Raum vor dem ersten oder nach dem letzten Composable.
  • ChainStyle.Packed: Der Abstand wird vor dem ersten und nach dem letzten Composable verteilt. Die Composables werden ohne Abstand zwischen ihnen zusammengepackt.