Układy niestandardowe

W Compose elementy interfejsu są reprezentowane przez funkcje kompozycyjne, które po wywołaniu emitują fragment interfejsu. Jest on następnie dodawany do drzewa interfejsu, które jest renderowane na ekranie. Każdy element interfejsu ma 1 element nadrzędny i potencjalnie wiele elementów podrzędnych. Każdy element znajduje się w elemencie nadrzędnym, określonym jako pozycja (x, y) i rozmiar, określony jako widthheight.

Rodzice określają ograniczenia dla elementów podrzędnych. Element jest proszony o określenie swojego rozmiaru w ramach tych ograniczeń. Ograniczenia określają minimalną i maksymalną widthheight 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, co zostało szczegółowo opisane w artykule Tworzenie niestandardowych układów.

Układanie każdego węzła w drzewie interfejsu to proces trzyetapowy. Każdy węzeł musi:

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

Trzy etapy układu węzłów: pomiar węzłów podrzędnych, określenie rozmiaru, umieszczenie węzłów podrzędnych

Zakresy określają kiedy możesz mierzyć i umieszczać dzieci. Pomiar układu można przeprowadzić tylko podczas pomiaru i przekazywania układu, a element podrzędny można umieścić tylko podczas przekazywania układu (i tylko po jego pomiarze). Ze względu na zakresy funkcji Compose, takie jak MeasureScope i PlacementScope, jest to egzekwowane w czasie kompilacji.

Używanie modyfikatora układu

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

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

Wyświetlmy na ekranie znak Text i określmy odległość od góry do linii bazowej pierwszego wiersza tekstu. Właśnie to robi modyfikator paddingFromBaseline. Używamy go tutaj jako przykładu. Aby to zrobić, użyj modyfikatora layout, aby ręcznie umieścić komponent na ekranie. Oto oczekiwane zachowanie, w którym ustawiono Text górny odstęp 24.dp:

Pokazuje różnicę między zwykłym dopełnieniem interfejsu, które określa odstęp między elementami, a dopełnieniem tekstu, które określa odstęp między kolejnymi liniami bazowymi.

Oto kod, który pozwala uzyskać taki odstęp:

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

Co się dzieje w tym kodzie:

  1. W parametrze lambda measurable mierzysz wartość Text reprezentowaną przez parametr mierzalny, wywołując funkcję measurable.measure(constraints).
  2. Rozmiar elementu kompozycyjnego określa się przez wywołanie metody layout(width, height), która udostępnia też wyrażenie lambda używane do umieszczania elementów opakowanych. W tym przypadku jest to wysokość między ostatnią linią bazową a dodanym dopełnieniem u góry.
  3. Elementy opakowane umieszcza się na ekranie, wywołując funkcję placeable.place(x, y). Jeśli zawinięte elementy nie zostaną umieszczone, nie będą widoczne. yPozycja odpowiada górnemu dopełnieniu – pozycji pierwszej linii bazowej tekstu.

Aby sprawdzić, czy działa on 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ły odstęp między elementami, a drugi odstęp od jednej linii bazowej do drugiej.

Tworzenie niestandardowych układów

Modyfikator layout zmienia tylko funkcję kompozycyjną wywołania. Aby zmierzyć i ułożyć wiele komponentów, użyj komponentu Layout. Ten komponent umożliwia ręczne mierzenie i układanie elementów podrzędnych. Wszystkie układy wyższego poziomu, takie jak ColumnRow, są tworzone za pomocą komponentu Layout.

.

Stwórzmy bardzo podstawową wersję Column. Większość układów niestandardowych ma następujący 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 pochodzące od elementu nadrzędnego. Zgodnie z tą samą logiką co wcześniej element 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
            }
        }
    }
}

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

Oto jak można użyć tego 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 komponentu, zmieniając lokalną kompozycję LocalLayoutDirection.

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

Podczas korzystania z funkcji layoutDirection umieszczaj komponenty kompozycyjne za pomocą funkcji 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 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 artykule Przykłady Compose, w których tworzone są niestandardowe układy.

Więcej informacji

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

Filmy