Układy niestandardowe

W Compose elementy interfejsu są reprezentowane przez funkcje typu „composable”, które po wywołaniu emitują fragment interfejsu, który jest następnie dodawany do drzewa interfejsu renderowanego na ekranie. Każdy element interfejsu ma 1 element nadrzędny i może mieć wiele elementów podrzędnych. Każdy element znajduje się też w elemencie nadrzędnym, określonym jako pozycja (x, y) i rozmiar, określony jako width i height.

Rodzice określają ograniczenia dotyczące elementów podrzędnych. Element musi określić swój rozmiar w ramach tych ograniczeń. Ograniczenia określają minimalną i maksymalną wartość width i height elementu. Jeśli element ma elementy podrzędne, może zmierzyć każdy z nich, aby określić swój rozmiar. Gdy element określi i zgłosi swój rozmiar, może zdefiniować sposób umieszczania elementów podrzędnych względem siebie, jak opisano szczegółowo w artykule Tworzenie układów niestandardowych.

Rozmieszczanie każdego węzła w drzewie interfejsu jest procesem składającym się z 3 etapów. Każdy węzeł musi:

  1. Mierzenie elementów podrzędnych
  2. Decyduj o rozmiarze
  3. Umieść elementy podrzędne
3 etapy układu węzła: mierzenie elementów podrzędnych, określanie rozmiaru, umieszczanie elementów podrzędnych
Rysunek 1. Trzy etapy układu węzła to: mierzenie elementów podrzędnych, określanie rozmiaru i umieszczanie elementów podrzędnych.

Użycie zakresów określa, kiedy możesz mierzyć i umieszczać elementy podrzędne. Układ można zmierzyć tylko podczas etapów pomiaru i układu, a element podrzędny można umieścić tylko podczas etapów układu (i tylko po jego zmierzeniu). Ze względu na zakresy Compose, takie jak MeasureScope i PlacementScope, jest to wymuszane w czasie kompilacji.

Używanie modyfikatora układu

Możesz użyć modyfikatora layout, aby zmienić sposób mierzenia i układania elementu. Modyfikator przyjmuje funkcję lambda. Jej parametry obejmują element, który można zmierzyć, przekazywany jako measurable, oraz ograniczenia przychodzące tego elementu kompozycyjnego, przekazywane jako constraints. Modyfikator układu niestandardowego może wyglądać tak:

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

Wyświetl Text na ekranie i kontroluj odległość od góry do linii bazowej pierwszego wiersza tekstu. Dokładnie to robi modyfikator paddingFromBaseline. Tutaj implementujesz go jako przykład. Aby to zrobić, użyj modyfikatora layout, aby ręcznie umieścić element kompozycyjny na ekranie. Oto wynik, gdy dopełnienie górne Text jest ustawione na 24.dp:

Ilustracja pokazująca różnicę między normalnym dopełnieniem interfejsu, które określa odstępy między elementami, a dopełnieniem tekstu, które określa odstępy między liniami bazowymi
Rysunek 2. Zastosowano tekst z: paddingFromBaseline.

Oto kod, który pozwala uzyskać 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 się dzieje w tym kodzie:

  1. W parametrze lambda measurable mierzysz Text reprezentowany przez mierzalny parametr, wywołując measurable.measure(constraints).
  2. Rozmiar elementu kompozycyjnego określasz, wywołując metodę layout(width, height), która udostępnia też funkcję lambda służącą do umieszczania elementów opakowanych. W tym przypadku wysokość to wysokość elementu Text plus placeableY, czyli firstBaselineToTop minus pierwsza linia bazowa elementu Text.
  3. Elementy opakowane umieszczasz na ekranie, wywołując placeable.placeRelative(x, y). Jeśli elementy zawijane nie zostaną umieszczone, nie będą widoczne. W układzie od prawej do lewej placeRelative odzwierciedla pozycję x; zobacz Kierunek układu. Pozycja y to firstBaselineToTop minus pierwsza linia bazowa tekstu, dzięki czemu pierwsza linia bazowa znajduje się w odległości firstBaselineToTop od góry.

Aby sprawdzić, czy działa to 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))
    }
}

Kilka podglądów elementów tekstowych; jeden pokazuje zwykłe dopełnienie między elementami, a drugi dopełnienie od jednej linii bazowej do następnej.
Rysunek 3. Modyfikator zastosowany do funkcji kompozycyjnej Text i wyświetlony w podglądzie.

Tworzenie układów niestandardowych

Modyfikator layout zmienia tylko wywołujący element kompozycyjny. Aby zmierzyć i rozmieścić wiele funkcji kompozycyjnych, użyj funkcji kompozycyjnej Layout. Ten element kompozycyjny umożliwia ręczne mierzenie i układanie elementów podrzędnych. Wszystkie układy wyższego poziomu, takie jak Column i Row, są tworzone za pomocą funkcji kompozycyjnej Layout.

W tym przykładzie tworzymy bardzo podstawową wersję Column. Większość układów niestandardowych ma taki wzorzec:

@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 zmierzyć, a constraints to ograniczenia z elementu nadrzędnego. Zgodnie z tą samą logiką co wcześniej funkcję MyBasicColumn można zaimplementować 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
            }
        }
    }
}

Komponenty podrzędne są ograniczone przez ograniczenia przychodzące Layout i są umieszczane na podstawie yPosition poprzedniej funkcji kompozycyjnej.

Oto jak można użyć tego niestandardowego elementu kompozycyjnego:

@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.
Rysunek 4. Niestandardowa implementacja usługi Column.

Kierunek układu

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

Jeśli ręcznie umieszczasz funkcje kompozycyjne na ekranie, LayoutDirection jest dostępny we właściwości layoutDirection elementu MeasureScope w modyfikatorze layout lub funkcji kompozycyjnej Layout.

Podczas korzystania z layoutDirection umieszczaj elementy kompozycyjne za pomocą place. W przeciwieństwie do metody placeRelative właściwość 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 praktyce

Więcej informacji o układach i modyfikatorach znajdziesz w artykule Podstawowe układy w Compose. Przykłady niestandardowych układów znajdziesz w przykładowych układach niestandardowych w Compose.

Więcej informacji

Więcej informacji o niestandardowych układach w Compose znajdziesz w tych materiałach:

Filmy