Układy niestandardowe

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

Elementy nadrzędne definiują ograniczenia dla elementów podrzędnych. Dochodzi do zdefiniowania rozmiaru elementu w ramach tych ograniczeń. Ograniczenia ograniczają minimalne i maksymalne wartości width i height elementu. Jeśli element ma elementy podrzędne, może mierzyć każdy element podrzędny, aby określić jego rozmiar. Gdy element określa i zgłasza własny rozmiar, można określić, jak względem niego rozmieszczać elementy podrzędne. Więcej informacji na ten temat znajdziesz w artykule Tworzenie układów niestandardowych.

Ułożenie każdego węzła w drzewie interfejsu obejmuje proces trzyetapowy. Każdy węzeł musi:

  1. Mierz wszystkie dzieci
  2. Określanie własnego rozmiaru
  3. Umieść elementy podrzędne

Trzy etapy układu węzłów: pomiar elementów podrzędnych, wybór rozmiaru, umieszczanie elementów podrzędnych

Użycie zakresów określa, kiedy możesz mierzyć i umieszczać dane dzieci. Pomiar układu można wykonać tylko podczas przechodzenia pomiarów i procesów układu, a element podrzędny można umieścić tylko w ramach przebiegu układu (i po jego zmierzeniu). Ze względu na zakresy tworzenia wiadomości, takie jak MeasureScope i PlacementScope, jest to wymuszane podczas kompilacji.

Używanie modyfikatora układu

Aby zmienić sposób pomiaru i układu elementu, możesz użyć modyfikatora layout. Funkcja Layout jest funkcją lambda. Jej parametry obejmują element, który można zmierzyć, przekazany jako measurable, oraz przychodzące ograniczenia elementu kompozycyjnego przekazywane jako constraints. Niestandardowy modyfikator układu może wyglądać tak:

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

Wyświetlmy na ekranie znak Text i ustawmy odległość od górnej krawędzi do punktu odniesienia. Właśnie tak działa modyfikator paddingFromBaseline. Zastosowaliśmy go tutaj jako przykład. Aby to zrobić, użyj modyfikatora layout i ręcznie umieść funkcję kompozycyjną na ekranie. Oto oczekiwane działanie, w przypadku którego dopełnienie u góry Text jest ustawione na 24.dp:

Pokazuje różnicę między normalnym dopełnieniem interfejsu, które ustawia odstęp między elementami, a dopełnieniem tekstu ustawiającym odstęp od punktu odniesienia do następnej.

Oto kod do utworzenia odstępów:

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 zawiera ten kod:

  1. W parametrze lambda measurable mierzysz wartość Text reprezentowaną przez parametr wymierny, wywołując funkcję measurable.measure(constraints).
  2. Rozmiar funkcji kompozycyjnej określasz, wywołując metodę layout(width, height), która zwraca też funkcję lambda służącą do umieszczania opakowanych elementów. W tym przypadku jest to wysokość między ostatnią bazą bazową a dodanym dopełnieniem u góry.
  3. Aby umieścić opakowane elementy na ekranie, wywołaj placeable.place(x, y). Jeśli opakowane elementy nie zostaną umieszczone, nie będą widoczne. Położenie y odpowiada górnemu dopełnieniu, czyli pozycji pierwszej linii bazowej tekstu.

Aby sprawdzić, czy wszystko działa zgodnie z oczekiwaniami, użyj tego modyfikatora w elemencie 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 dopełnienie między elementami, a drugi – dopełnienie od jednego elementu bazowego do drugiego.

Tworzenie układów niestandardowych

Modyfikator layout zmienia tylko funkcję wywoływania kompozycyjnego. Aby mierzyć i układać wiele elementów kompozycyjnych, użyj funkcji kompozycyjnej Layout. Umożliwia on ręczne pomiary i rozmieszczanie dzieci. Wszystkie układy wyższego poziomu, takie jak Column i Row, korzystają z funkcji kompozycyjnej Layout.

Stwórzmy podstawową wersję interfejsu Column. Większość układów niestandardowych korzysta z tego wzoru:

@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 modyfikator layout measurables to lista elementów podrzędnych, które chcesz zmierzyć, a constraints to ograniczenia z elementu nadrzędnego. Zgodnie z tą samą logiką co wcześniej interfejs 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
            }
        }
    }
}

Podrzędne elementy kompozycyjne są ograniczone przez ograniczenia Layout (bez ograniczeń minHeight) i są umieszczane na podstawie funkcji yPosition poprzedniej funkcji kompozycyjnej.

Oto jak można wykorzystać ten niestandardowy element kompozycyjny:

@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 funkcji kompozycyjnej, zmieniając lokalną kompozycję LocalLayoutDirection.

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

Jeśli używasz właściwości layoutDirection, do tworzenia funkcji kompozycyjnych używaj funkcji place. W przeciwieństwie do metody placeRelative 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 sekcji Podstawowe układy w narzędziu Compose. Działanie układów niestandardowych znajdziesz w artykule Tworzenie układów niestandardowych.

Więcej informacji

Więcej informacji o układach niestandardowych w funkcji tworzenia wiadomości znajdziesz w tych dodatkowych materiałach.

Filmy