ConstraintLayout in Compose

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

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. In Compose ist dies jedoch kein Problem, da tiefe Layouthierarchien effizient verarbeitet werden können. ConstraintLayout ist daher nicht so vorteilhaft.

Einführung in ConstraintLayout

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

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

ConstraintLayout in Compose funktioniert mit einer DSL so:

  • Erstellen Sie mit createRefs() oder createRefFor() Referenzen für jede zusammensetzbare Funktion im ConstraintLayout.
  • Einschränkungen werden mit dem Modifikator constrainAs() angegeben, der die Referenz als Parameter verwendet und es Ihnen ermöglicht, die Einschränkungen im Body-Lambda anzugeben.
  • Einschränkungen werden mit linkTo() oder anderen hilfreichen Methoden angegeben.
  • parent ist eine vorhandene Referenz, mit der Einschränkungen für die komponierbare Funktion ConstraintLayout selbst angegeben werden können.

Hier ein Beispiel für eine zusammensetzbare Funktion, die ein ConstraintLayout verwendet:

@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 den oberen Rand des Button-Elements auf das übergeordnete Element mit einem Abstand von 16.dp und ein Text-Element auf den unteren Rand des Button-Elements ebenfalls mit einem Abstand von 16.dp ein.

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

Entkoppelte API

Im Beispiel ConstraintLayout werden Einschränkungen inline mit einem Modifikator in der zusammensetzbaren Funktion angegeben, auf die 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. Beispielsweise können Sie 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 ConstraintLayout einen ConstraintSet als Parameter.
  2. Weisen Sie die in ConstraintSet erstellten Referenzen mit dem Modifikator layoutId Composables zu.

@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 eine andere ConstraintSet übergeben.

ConstraintLayout Konzepte

ConstraintLayout enthält Konzepte wie Richtlinien, Barrieren und Ketten, die Ihnen bei der Positionierung von Elementen in Ihrer zusammensetzbaren Funktion helfen können.

Richtlinien

Hilfslinien sind kleine visuelle Hilfen, die Sie beim Entwerfen von Layouts unterstützen. Composables können auf eine Hilfslinie beschränkt werden. Hilfslinien sind nützlich, um Elemente an einer bestimmten dp oder percentage innerhalb der übergeordneten zusammensetzbaren Funktion zu positionieren.

Es gibt zwei verschiedene Arten von Hilfslinien: vertikale und horizontale. Die beiden horizontalen sind top und bottom, die beiden vertikalen sind 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 zum Erstellen einer Richtlinie createGuidelineFrom* mit dem erforderlichen Richtlinientyp. Dadurch wird eine Referenz erstellt, die im Block Modifier.constrainAs() verwendet werden kann.

Barrieren

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

Verwenden Sie zum Erstellen einer Barriere 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 davon eingeschränkt werden.

Verwenden Sie zum Erstellen einer Kette entweder createVerticalChain oder createHorizontalChain:

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 Modifier.constrainAs()-Block verwendet werden.

Eine Kette kann mit verschiedenen ChainStyles-Werten konfiguriert werden, die festlegen, wie der Platz um eine komponierbare Funktion herum behandelt wird, z. B.:

  • ChainStyle.Spread: Der Platz wird gleichmäßig auf alle Composables verteilt, einschließlich des leeren Platzes vor dem ersten und nach dem letzten Composable.
  • ChainStyle.SpreadInside: Der Platz wird gleichmäßig auf alle Composables verteilt, ohne dass vor dem ersten oder nach dem letzten Composable leerer Platz bleibt.
  • ChainStyle.Packed: Der Platz wird vor dem ersten und nach dem letzten Composable verteilt. Die Composables werden ohne Platz dazwischen zusammengepackt.