Układy niestandardowe

W Compose elementy interfejsu są reprezentowane przez funkcje kompozytowe, które po wywołaniu emitują element interfejsu. Następnie jest on dodawany do drzewa interfejsu, które jest renderowane na ekranie. Każdy element interfejsu ma jednego rodzica i potencjalnie wiele elementów podrzędnych. Każdy element jest też umieszczony w swoim elemencie nadrzędnym, z położeniem określonym jako współrzędne (x, y) oraz rozmiarem określonym jako width i height.

Rodzice definiują ograniczenia dla elementów podrzędnych. Element musi określić swój rozmiar w ramach tych ograniczeń. Ograniczenia ograniczają minimalną i maksymalną wartość width oraz height elementu. Jeśli element ma elementy podrzędne, może mierzyć każdy z nich, aby określić swój rozmiar. Gdy element określi i zgłosi swój rozmiar, może określić, jak umieścić elementy podrzędne względem siebie. Szczegółowe informacje na ten temat znajdziesz w artykule Tworzenie niestandardowych układów.

Umieszczanie każdego węzła w drzewie interfejsu to proces 3-etapowy. Każdy węzeł musi:

  1. Pomiar elementów podrzędnych
  2. określać własny rozmiar,
  3. umieszczać swoje elementy podrzędne.

3 kroki układu węzła: pomiar podrzędnych, określenie rozmiaru, umieszczenie podrzędnych

Zastosowanie zakresów określa, kiedy możesz mierzyć i umieszczać dzieci. Pomiar układu można przeprowadzić tylko podczas pomiaru i przesyłania układu, a element podrzędny można umieścić tylko podczas przesyłania układu (i tylko po jego zmierzeniu). Ze względu na zakresy tworzenia takich jak MeasureScope i PlacementScope jest to egzekwowane w momencie kompilacji.

Używanie modyfikatora układu

Za pomocą modyfikatora layout możesz zmienić sposób pomiaru i układania elementu. Layout to funkcja lambda, której parametry obejmują element, który możesz mierzyć (przekazywany jako measurable), oraz docierające do niego ograniczenia (przekazywane jako constraints). Modyfikator niestandardowego układu może wyglądać tak:

fun Modifier.customLayoutModifier() =
    layout { measurable, constraints ->
        // ...
    }

Wyświetlmy na ekranie Text i zmodyfikujmy odległość od górnej krawędzi do linii bazowej pierwszego wiersza tekstu. Właśnie to robi modyfikator paddingFromBaseline, który implementujemy tutaj jako przykład. Aby to zrobić, użyj modyfikatora layout, aby ręcznie umieścić komponent na ekranie. Oto oczekiwane zachowanie, gdy ustawiono wypełnienie górne Text: 24.dp

Pokazuje różnicę między normalnym wypełnieniem interfejsu, które ustawia odstęp między elementami, a wypełnieniem tekstu, które ustawia odstęp od jednej linii bazowej do następnej.

Oto kod, który powoduje takie odstępy:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

Oto, co dzieje się w tym kodzie:

  1. W parametrze lambda measurable mierzysz wartość Text reprezentowaną przez parametr do pomiaru, wywołując funkcję measurable.measure(constraints).
  2. Rozmiar elementu kompozytowego określasz, wywołując metodę layout(width, height), która zwraca też funkcję lambda używaną do umieszczania owiniętych elementów. W tym przypadku jest to wysokość między ostatnią linią bazową a dodaną górną przestrzenią.
  3. Aby ustawić opakowane elementy na ekranie, wywołaj funkcję placeable.place(x, y). Jeśli owinięte elementy nie zostaną umieszczone, nie będą widoczne. Pozycja y odpowiada górnemu wypełnieniu – pozycji pierwszej linii tekstu.

Aby sprawdzić, czy wszystko działa zgodnie z oczekiwaniami, użyj tego modyfikatora w przypadku Text:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

Wiele podglądów elementów tekstowych: jeden pokazuje zwykłe wypełnienie między elementami, a drugi – wypełnienie od jednej linii bazowej do następnej

Tworzenie układów niestandardowych

Modyfikator layout zmienia tylko wywoływane składane. Aby mierzyć i umieszczać wiele komponentów, użyj komponentu Layout. Ta kompozycja umożliwia ręczne pomiar i rozmieszczenie elementów podrzędnych. Wszystkie układy wyższego poziomu, takie jak ColumnRow, są tworzone za pomocą komponentu Layout.

Utwórzmy bardzo podstawową wersję Column. Większość układów niestandardowych ma taki schemat:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
        // ...
    }
}

Podobnie jak w przypadku modyfikatora layout, measurables to lista elementów podrzędnych, które należy mierzyć, a constraints to ograniczenia z elementu nadrzędnego. Zgodnie z tą samą logiką co wcześniej MyBasicColumn można zastosować w ten sposób:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

Elementy składowe podrzędne są ograniczone przez ograniczenia Layout (bez ograniczeń minHeight) i są umieszczane na podstawie yPosition poprzedniego elementu składowego.

Oto, jak można używać niestandardowego komponentu:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

Kilka elementów tekstowych ułożonych jeden nad drugim w kolumnie.

Kierunek układu

Zmień kierunek układu komponenta, zmieniając kompozycję lokalną LocalLayoutDirection.

Jeśli funkcje kompozycyjne umieszczasz ręcznie na ekranie, LayoutDirection jest częścią LayoutScope modyfikatora layout lub funkcji kompozycyjnej Layout.

Podczas korzystania z funkcji layoutDirection umieszczaj komponenty za pomocą elementu place. W przeciwieństwie do metody placeRelative wartość place nie zmienia się w zależności od kierunku układu (od lewej do prawej lub od prawej do lewej).

Układy niestandardowe w akcji

Więcej informacji o układach i modyfikatorach znajdziesz w artykule Układy podstawowe w Compose, a o niestandardowych układach dowiesz się więcej z artykułu Przykłady tworzenia niestandardowych układów w Compose.

Więcej informacji

Więcej informacji o niestandardowych układach w edytorze kompozycji znajdziesz w tych dodatkowych materiałach.

Filmy