Modyfikatory tworzenia wiadomości

Modyfikatory pozwalają dekorować lub rozszerzać funkcje kompozycyjne. Modyfikatory umożliwiają wykonywanie tych czynności:

  • zmiana rozmiaru, układu, zachowania i wyglądu funkcji kompozycyjnej;
  • dodawanie informacji, np. etykiet ułatwień dostępu;
  • przetwarzanie danych wejściowych użytkownika;
  • dodawanie interakcji wyższego poziomu, np. umożliwianie klikania, przewijania, przeciągania lub powiększania elementu.

Modyfikatory to standardowe obiekty Kotlin. Aby utworzyć modyfikator, wywołaj jedną z Modifier funkcji klasy:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Dwa wiersze tekstu na kolorowym tle z marginesem wokół tekstu.

Możesz łączyć te funkcje, aby je komponować:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Kolorowe tło za tekstem zajmuje teraz całą szerokość urządzenia.

W powyższym kodzie zwróć uwagę na różne funkcje modyfikatorów używane razem.

  • padding dodaje odstęp wokół elementu.
  • fillMaxWidth sprawia, że funkcja kompozycyjna wypełnia maksymalną szerokość podaną przez element nadrzędny.

Najlepszym rozwiązaniem jest, aby wszystkie funkcje kompozycyjne akceptowały parametr modifier i przekazywały ten modyfikator do pierwszego elementu podrzędnego, który emituje interfejs. Dzięki temu kod jest bardziej wielokrotnego użytku, a jego zachowanie bardziej przewidywalne i intuicyjne. Więcej informacji znajdziesz w wytycznych dotyczących interfejsu Compose API w sekcji Elementy akceptują i respektują parametr Modifier.

Kolejność modyfikatorów ma znaczenie

Kolejność funkcji modyfikatorów jest istotna. Każda funkcja wprowadza zmiany w Modifier zwracanym przez poprzednią funkcję, dlatego kolejność wpływa na wynik końcowy. Zobaczmy przykład:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Cały obszar, w tym marginesy wokół krawędzi, reaguje na kliknięcia.

W powyższym kodzie klikalny jest cały obszar, w tym otaczający go odstęp, ponieważ modyfikator padding został zastosowany po modyfikatorze clickable. Jeśli kolejność modyfikatorów zostanie odwrócona, odstęp dodany przez padding nie będzie reagować na dane wejściowe użytkownika:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Margines wokół krawędzi układu nie reaguje już na kliknięcia.

Wbudowane modyfikatory

Jetpack Compose udostępnia listę wbudowanych modyfikatorów, które pomagają dekorować lub rozszerzać funkcje kompozycyjne. Oto kilka typowych modyfikatorów, których będziesz używać do dostosowywania układów.

padding i size

Domyślnie układy udostępniane w Compose otaczają swoje elementy podrzędne. Możesz jednak ustawić rozmiar za pomocą modyfikatora size:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

Pamiętaj, że określony rozmiar może nie być respektowany, jeśli nie spełnia ograniczeń pochodzących od elementu nadrzędnego układu. Jeśli chcesz, aby rozmiar funkcji kompozycyjnej był stały niezależnie od ograniczeń przychodzących, użyj modyfikatora requiredSize:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

Obraz podrzędny jest większy niż ograniczenia pochodzące z elementu nadrzędnego

W tym przykładzie nawet jeśli element nadrzędny height jest ustawiony na 100.dp, wysokość elementu Image będzie wynosić 150.dp, ponieważ modyfikator requiredSize ma wyższy priorytet.

Jeśli chcesz, aby układ podrzędny wypełniał całą dostępną wysokość dozwoloną przez element nadrzędny, dodaj modyfikator fillMaxHeight (Compose udostępnia też fillMaxSize i fillMaxWidth):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

Wysokość obrazu jest taka sama jak wysokość elementu nadrzędnego

Aby dodać odstęp wokół elementu, ustaw modyfikator padding.

Jeśli chcesz dodać odstęp nad linią bazową tekstu, aby uzyskać określoną odległość od górnej krawędzi układu do linii bazowej, użyj modyfikatora paddingFromBaseline:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

Tekst z wypełnieniem nad nim

Odliczenie

Aby ustawić układ względem jego pierwotnego położenia, dodaj offset modyfikator i ustaw przesunięcie na osi x i y. Przesunięcia mogą być dodatnie i niedodatnie. Różnica między padding a offset polega na tym, że dodanie offset do funkcji kompozycyjnej nie zmienia jej wymiarów:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Tekst przesunięty na prawą stronę kontenera nadrzędnego

Modyfikator offset jest stosowany w poziomie zgodnie z kierunkiem układu. W kontekście od lewej do prawej dodatni offset przesuwa element w prawo, a w kontekście od prawej do lewej – w lewo. Jeśli chcesz ustawić przesunięcie bez uwzględniania kierunku układu, użyj absoluteOffset modyfikatora, w którym dodatnia wartość przesunięcia zawsze przesuwa element w prawo.

Modyfikator offset udostępnia 2 przeciążenia: offset, które przyjmuje przesunięcia jako parametry, oraz offset, które przyjmuje lambdę. Więcej informacji o tym, kiedy używać każdego z nich i jak optymalizować wydajność, znajdziesz w sekcji Wydajność Compose – jak najdłuższe odraczanie odczytów.

Bezpieczeństwo zakresu w Compose

W Compose są modyfikatory, których można używać tylko wtedy, gdy są stosowane do elementów podrzędnych określonych funkcji kompozycyjnych. Compose wymusza to za pomocą zakresów niestandardowych.

Jeśli na przykład chcesz, aby element podrzędny był tak duży jak element nadrzędny Box bez wpływu na rozmiar Box, użyj matchParentSize modyfikatora. matchParentSize jest dostępny tylko w BoxScope. Dlatego można go używać tylko w przypadku elementu podrzędnego w elemencie nadrzędnym Box.

Bezpieczeństwo zakresu uniemożliwia dodawanie modyfikatorów, które nie działałyby w innych funkcjach kompozycyjnych i zakresach, oraz oszczędza czas na próby i błędy.

Modyfikatory w zakresie informują element nadrzędny o pewnych informacjach, które powinien on znać o elemencie podrzędnym. Są one też często nazywane modyfikatorami danych nadrzędnych. Ich wewnętrzna struktura różni się od modyfikatorów ogólnego przeznaczenia, ale z punktu widzenia użytkowania te różnice nie mają znaczenia.

matchParentSize w Box

Jak wspomnieliśmy powyżej, jeśli chcesz, aby układ podrzędny miał taki sam rozmiar jak element nadrzędny Box, ale bez wpływu na rozmiar Box, użyj modyfikatora matchParentSize.

Pamiętaj, że matchParentSize jest dostępny tylko w zakresie Box, co oznacza, że ma zastosowanie tylko do bezpośrednich elementów podrzędnych funkcji kompozycyjnych Box.

W przykładzie poniżej element podrzędny Spacer przyjmuje rozmiar od elementu nadrzędnego Box, który z kolei przyjmuje rozmiar od największych elementów podrzędnych, w tym przypadku ArtistCard.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

Szare tło wypełniające kontener

Jeśli zamiast matchParentSize użyto by fillMaxSize, element Spacer zająłby całą dostępną przestrzeń dozwoloną dla elementu nadrzędnego, co spowodowałoby rozszerzenie elementu nadrzędnego i wypełnienie całej dostępnej przestrzeni.

Szare tło wypełniające ekran

weight w Row i Column

Jak widać w poprzedniej sekcji dotyczącej odstępu i rozmiaru, domyślnie rozmiar funkcji kompozycyjnej jest określany przez otaczaną przez nią treść. Możesz ustawić rozmiar funkcji kompozycyjnej tak, aby był elastyczny w obrębie elementu nadrzędnego, używając modyfikatora weight, który jest dostępny tylko w RowScope i ColumnScope.

Weźmy na przykład Row, który zawiera 2 funkcje kompozycyjne Box. Pierwszy element Box ma 2 razy większą weight niż drugi, więc ma 2 razy większą szerokość. Ponieważ Row ma szerokość 210.dp, pierwszy element Box ma szerokość 140.dp, a drugi 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

Szerokość obrazu jest 2 razy większa niż szerokość tekstu

Wyodrębnianie i ponowne używanie modyfikatorów

Można łączyć ze sobą wiele modyfikatorów, aby dekorować lub rozszerzać funkcje kompozycyjne. Ten łańcuch jest tworzony za pomocą interfejsu Modifier który reprezentuje uporządkowaną, niezmienną listę pojedynczych elementów Modifier.Elements.

Każdy element Modifier.Element reprezentuje indywidualne zachowanie, takie jak układ, rysowanie i grafika, wszystkie gesty, fokus i semantyka, a także zdarzenia wejściowe urządzenia. Ich kolejność ma znaczenie: elementy modyfikatorów dodane jako pierwsze zostaną zastosowane jako pierwsze.

Czasami warto ponownie używać tych samych instancji łańcucha modyfikatorów w wielu funkcjach kompozycyjnych, wyodrębniając je do zmiennych i przenosząc do zakresów wyższych. Może to poprawić czytelność kodu lub wydajność aplikacji z kilku powodów:

  • Ponowne przydzielanie modyfikatorów nie będzie powtarzane, gdy nastąpi ponowne komponowanie funkcji kompozycyjnych, które ich używają.
  • Łańcuchy modyfikatorów mogą być bardzo długie i złożone, dlatego ponowne używanie tej samej instancji łańcucha może zmniejszyć obciążenie środowiska wykonawczego Compose podczas porównywania ich.
  • To wyodrębnianie zwiększa przejrzystość, spójność i łatwość utrzymania kodu.

Sprawdzone metody ponownego używania modyfikatorów

Twórz własne łańcuchy Modifier i wyodrębniaj je, aby ponownie używać ich w wielu komponentach kompozycyjnych. Możesz po prostu zapisać modyfikator, ponieważ są to obiekty podobne do danych:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

Wyodrębnianie i ponowne używanie modyfikatorów podczas obserwowania często zmieniającego się stanu

Podczas obserwowania często zmieniających się stanów w funkcjach kompozycyjnych, takich jak stany animacji lub scrollState, może dojść do znacznej liczby ponownych komponowań. W takim przypadku modyfikatory będą przydzielane przy każdym ponownym komponowaniu i potencjalnie przy każdej klatce:

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

Zamiast tego możesz utworzyć, wyodrębnić i ponownie użyć tej samej instancji modyfikatora i przekazać ją do funkcji kompozycyjnej w ten sposób:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

Wyodrębnianie i ponowne używanie modyfikatorów bez zakresu

Modyfikatory mogą być bez zakresu lub w zakresie określonej funkcji kompozycyjnej. W przypadku modyfikatorów bez zakresu możesz je łatwo wyodrębnić poza funkcje kompozycyjne jako proste zmienne:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

Może to być szczególnie korzystne w połączeniu z układami Lazy. W większości przypadków chcesz, aby wszystkie elementy, których może być dużo, miały dokładnie te same modyfikatory:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

Wyodrębnianie i ponowne używanie modyfikatorów w zakresie

W przypadku modyfikatorów, które są w zakresie określonych funkcji kompozycyjnych, możesz je wyodrębnić do najwyższego możliwego poziomu i ponownie użyć w odpowiednich miejscach:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

Wyodrębnione modyfikatory w zakresie należy przekazywać tylko do bezpośrednich elementów podrzędnych w tym samym zakresie. Więcej informacji o tym, dlaczego jest to ważne, znajdziesz w sekcji Bezpieczeństwo zakresu w Compose:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

Dalsze łączenie wyodrębnionych modyfikatorów

Możesz dalej łączyć lub dołączać wyodrębnione łańcuchy modyfikatorów, wywołując funkcję .then():

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

Pamiętaj, że kolejność modyfikatorów ma znaczenie.

Więcej informacji

Udostepniamy pełną listę modyfikatorów z ich parametrami i zakresami.

Aby poćwiczyć używanie modyfikatorów, możesz też przejść przez ćwiczenie z programowania Podstawowe układy w Compose lub zapoznać się z repozytorium Now in Android.

Więcej informacji o modyfikatorach niestandardowych i sposobach ich tworzenia znajdziesz w dokumentacji Układy niestandardowe – używanie modyfikatora układu.