Układ ograniczeń w tworzeniu

ConstraintLayout to układ, który umożliwia umieszczanie elementów kompozycyjnych względem innych elementów kompozycyjnych na ekranie. Jest to alternatywa dla używania wielu zagnieżdżonych Row, Column, Box, i innych niestandardowych elementów układu.

W systemie View ConstraintLayout był zalecanym sposobem tworzenia dużych i złożonych układów, ponieważ płaska hierarchia widoków była wydajniejsza niż zagnieżdżone widoki. Nie jest to jednak problem w Compose, który może efektywnie obsługiwać głębokie hierarchie układów, więc ConstraintLayout nie jest tak korzystny.

Rozpoczęcie pracy z ConstraintLayout

Aby używać ConstraintLayout w Compose, musisz dodać tę zależność w pliku build.gradle (oprócz konfiguracji Compose):

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

ConstraintLayout w Compose działa w ten sposób, że używa DSL:

  • Utwórz odniesienia do każdego elementu kompozycyjnego w ConstraintLayout za pomocą createRefs() lub createRefFor().
  • Ograniczenia są podawane za pomocą modyfikatora constrainAs(), który przyjmuje odniesienie jako parametr i umożliwia określenie jego ograniczeń w lambdzie treści.
  • Ograniczenia są określane za pomocą linkTo() lub innych przydatnych metod.
  • parent to istniejące odniesienie, którego można użyć do określenia ograniczeń względem samego elementu kompozycyjnego ConstraintLayout.

Oto przykład elementu kompozycyjnego używającego 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)
            }
        )
    }
}

Ten kod ogranicza górną krawędź Button do elementu nadrzędnego z marginesem 16.dp, a Text do dolnej krawędzi Button również z marginesem 16.dp.

Przycisk pojawia się nad tekstem
Rysunek 1. Elementy kompozycyjne Button i Text ograniczone względem siebie w ConstraintLayout.

Oddzielony interfejs API

W przykładzie ConstraintLayout ograniczenia są określane w tekście za pomocą modyfikatora w elemencie kompozycyjnym, do którego są stosowane. W pewnych sytuacjach lepiej jest jednak oddzielić ograniczenia od układów, do których są stosowane. Możesz na przykład chcieć zmienić ograniczenia na podstawie konfiguracji ekranu lub animować przejścia między 2 zestawami ograniczeń.

W takich przypadkach możesz użyć ConstraintLayout w inny sposób:

  1. Przekaż ConstraintSet jako parametr do ConstraintLayout.
  2. Przypisz odniesienia utworzone w ConstraintSet do elementów kompozycyjnych za pomocą layoutId modyfikatora.

@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)
        }
    }
}

Gdy trzeba zmienić ograniczenia, wystarczy przekazać inny ConstraintSet.

Pojęcia związane z ConstraintLayout

ConstraintLayout zawiera takie pojęcia jak linie pomocnicze, bariery i łańcuchy, które mogą pomóc w pozycjonowaniu elementów w elemencie kompozycyjnym.

Linie pomocnicze

Linie pomocnicze to małe wizualne elementy pomocnicze do projektowania układów. Elementy kompozycyjne można ograniczyć do linii pomocniczej. Linie pomocnicze są przydatne do pozycjonowania elementów w a certain dp or percentage inside the parent composable.

Istnieją 2 rodzaje linii pomocniczych: pionowe i poziome. 2 poziome to top i bottom, a 2 pionowe to start i 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)
}

Aby utworzyć linię pomocniczą, użyj createGuidelineFrom* z wymaganym typem linii pomocniczej. Spowoduje to utworzenie odniesienia, którego można użyć w bloku Modifier.constrainAs().

Bariery

Bariery odwołują się do wielu elementów kompozycyjnych, aby utworzyć wirtualną linię pomocniczą na podstawie najbardziej skrajnego widżetu po określonej stronie.

Aby utworzyć barierę, użyj createTopBarrier() (lub createBottomBarrier(), createEndBarrier(), createStartBarrier()) i podaj odniesienia, które mają tworzyć barierę.

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

        val topBarrier = createTopBarrier(button, text)
    }
}

Barierę można następnie użyć w bloku Modifier.constrainAs().

rozważ użycie pomiarów wewnętrznych.

Łańcuchy

Łańcuchy zapewniają zachowanie podobne do grupy w jednej osi (poziomej lub pionowej). Drugą oś można ograniczyć niezależnie.

Aby utworzyć łańcuch, użyj createVerticalChain lub 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)
    }
}

Łańcuch można następnie użyć w bloku Modifier.constrainAs().

Łańcuch można skonfigurować za pomocą różnych ChainStyles, które określają, jak postępować z miejscem otaczającym element kompozycyjny, np.:

  • ChainStyle.Spread: miejsce jest równomiernie rozłożone na wszystkie elementy kompozycyjne, w tym wolne miejsce przed pierwszym elementem kompozycyjnym i po ostatnim.
  • ChainStyle.SpreadInside: miejsce jest równomiernie rozłożone na wszystkie elementy kompozycyjne, bez wolnego miejsca przed pierwszym elementem kompozycyjnym i po ostatnim.
  • ChainStyle.Packed: miejsce jest rozłożone przed pierwszym i po ostatnim elementem kompozycyjnym, a elementy kompozycyjne są spakowane bez miejsca między nimi.