Modyfikatory tworzenia wiadomości

Modyfikatory umożliwiają ozdabianie lub rozszerzanie komponentów. Dzięki modyfikatorom możesz:

  • Zmienianie rozmiaru, układu, zachowania i wyglądu komponentu
  • Dodawanie informacji, takich jak etykiety ułatwień dostępu
  • Przetwarzanie danych wejściowych użytkownika
  • Dodawanie interakcji na wysokim poziomie, np. klikania, przewijania, przeciągania lub powiększania elementów.

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

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

2 wiersze tekstu na kolorowym tle z odstępem wokół tekstu.

Możesz łączyć te funkcje, aby tworzyć z nich złożone wyrażenia:

@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 modyfikujące używane razem.

  • padding umieszcza odstęp wokół elementu.
  • fillMaxWidth powoduje, że kompozyt wypełnia maksymalną szerokość określoną przez jego nadrzędny element.

Zalecamy, aby wszystkie komponenty przyjmowały parametr modifier i przekazywały ten modyfikator do pierwszego podrzędnego elementu, który emituje interfejs użytkownika. Dzięki temu kod będzie można łatwiej wykorzystać ponownie, a jego działanie będzie bardziej przewidywalne i intuicyjne. Więcej informacji znajdziesz w wytycznych dotyczących interfejsu Compose API, w sekcji Elementy akceptują i szanują parametr Modifier.

Kolejność modyfikatorów ma znaczenie

Kolejność funkcji modyfikujących ma znaczenie. Każda funkcja wprowadza zmiany w funkcji Modifier zwróconej przez poprzednią funkcję, dlatego sekwencja wpływa na ostateczny wynik. Spójrzmy na 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 odstęp od krawędzi, reaguje na kliknięcia

W powyższym kodzie cała obszar jest klikalny, w tym obramowanie, ponieważ modyfikator padding został zastosowany po modyfikatorze clickable. Jeśli kolejność modyfikatorów zostanie odwrócona, przestrzeń dodana przez padding nie reaguje 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
    }
}

Odstęp wokół krawędzi układu nie reaguje już na kliknięcia

Wbudowane modyfikatory

Jetpack Compose udostępnia listę wbudowanych modyfikatorów, które ułatwiają dekorowanie i rozszerzanie komponentów. Oto kilka typowych modyfikatorów, których użyjesz do dostosowywania układów.

paddingsize

Domyślnie układy podane w sekcji Tworzenie obejmują 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 podany przez Ciebie rozmiar może nie zostać uwzględniony, jeśli nie spełnia ograniczeń narzuconych przez nadrzędny układ. Jeśli chcesz, aby rozmiar kompozycyjny został ustalony niezależnie od przychodzących ograniczeń, 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 obrazu nadrzędnego

W tym przykładzie, nawet jeśli wartość nadrzędnego height zostanie ustawiona na 100.dp, wysokość elementu Image będzie wynosić 150.dp, ponieważ modyfikator requiredSize ma pierwszeństwo.

Jeśli chcesz, by układ podrzędny wypełniał całą dostępną wysokość dozwoloną w przypadku elementu nadrzędnego, dodaj modyfikator fillMaxHeight (kompozycje obejmują również wartości 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ć wypełnienie wokół elementu, ustaw modyfikator padding.

Jeśli chcesz dodać wypełnienie nad poziomą linią tekstu, aby uzyskać określoną odległość od górnej krawędzi układu do tej linii, 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

Przesunięcie

Aby ustawić układ względem jego pierwotnego położenia, dodaj modyfikator offset i ustaw przesunięcie wzdłuż osi xy. Odsunięcia mogą być dodatnie lub ujemne. Różnica między paddingoffset polega na tym, że dodanie offset do kompozytowego nie zmienia jego pomiaró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 poziomo zgodnie z kierunkiem układu. W kontekście od lewej do prawej dodatnia wartość 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 modyfikatora absoluteOffset, w którym dodatnia wartość przesunięcia zawsze przesuwa element w prawo.

Modyfikator offset ma 2 przeciążenia: offset, który przyjmuje przesunięcia jako parametry, oraz offset, który przyjmuje funkcję lambda. Więcej informacji o tym, kiedy i jak używać tych funkcji oraz jak optymalizować skuteczność, znajdziesz w sekcji Skuteczność tworzenia – opóźnianie odczytów tak długo, jak to możliwe.

Bezpieczeństwo zakresu w Compose

W Compose dostępne są modyfikatory, które można stosować tylko do elementów podrzędnych niektórych komponentów. Funkcja tworzenia wymusza to za pomocą zakresów niestandardowych.

Jeśli na przykład chcesz, aby element potomny był tak duży jak element nadrzędny Box, ale nie wpływało to na rozmiar Box, użyj modyfikatora matchParentSize. Funkcja matchParentSize jest dostępna tylko w BoxScope. Dlatego można go używać tylko na koncie podrzędnym w ramach Box.

Zabezpieczenie zakresu uniemożliwia dodawanie modyfikatorów, które nie działają w innych składaniach i zakresach, a także pozwala zaoszczędzić czas, który trzeba poświęcić na próby i błędy.

Modyfikatory ograniczone informują rodzica o niektórych informacjach dotyczących dziecka. Są one też często nazywane modyfikatorami danych nadrzędnych. Różnice wewnętrzne różnią się od modyfikatorów do ogólnego przeznaczenia, ale z perspektywy 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ł ten sam rozmiar co układ nadrzędny Box bez wpływu na rozmiar elementu Box, użyj modyfikatora matchParentSize.

Pamiętaj, że funkcja matchParentSize jest dostępna tylko w zakresie Box, co oznacza, że dotyczy tylko bezpośrednich elementów podrzędnych elementów kompozycyjnych Box.

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

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

Szary kolor tła wypełniający kontener

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

Szary ekran wypełniony tłem

weight w Row i Column

Jak już wiesz z poprzedniej sekcji dotyczącej wypełniania i rozmiaru, domyślnie rozmiar kompozytowy jest definiowany przez zawartość, którą otacza. Możesz ustawić rozmiar funkcji kompozytowej tak, aby był elastyczny w ramach 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 komponenty Box. Pierwsze pole ma dwukrotnie większą wartość weight niż drugie, więc ma też dwukrotnie większą szerokość. Szerokość elementu Row to 210.dp, więc pierwszy 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 równa podwójnej szerokości tekstu.

Wyodrębnianie i ponowne wykorzystywanie modyfikatorów

Aby udekorować lub rozszerzyć kompozyt, można połączyć łańcuchowo kilka modyfikatorów. Ten łańcuch jest tworzony za pomocą interfejsu Modifier, który reprezentuje uporządkowaną, niezmienną listę pojedynczych Modifier.Elements.

Każdy element Modifier.Element reprezentuje indywidualne zachowanie, takie jak zachowanie związane z układem, rysowaniem i grafiką, wszystkie zachowania związane z gestami, zachowanie związane z fokusem i semantyką, a także zdarzenia wprowadzania danych na urządzeniu. Ich kolejność ma znaczenie: najpierw zostaną zastosowane elementy modyfikujące dodane jako pierwsze.

Czasami warto ponownie użyć tych samych instancji łańcucha modyfikatorów w kilku komponentach, wyodrębniając je w postaci zmiennych i przenosząc do szerszych zakresów. Może to poprawić czytelność kodu lub pomóc w zwiększeniu wydajności aplikacji z kilku powodów:

  • ponowne przydzielenie modyfikatorów nie będzie powtarzane podczas ponownego tworzenia kompozytowanych elementów, które ich używają.
  • Łańcuchy modyfikatorów mogą być bardzo długie i skomplikowane, więc ponowne użycie tego samego łańcucha może zmniejszyć obciążenie, jakie Compose musi ponieść podczas ich porównywania.
  • Takie wyodrębnienie przyczynia się do przejrzystości, spójności i łatwiejszego utrzymywania kodu w całej bazie kodu.

Sprawdzone metody ponownego używania modyfikatorów

Twórz własne łańcuchy Modifier i wyodbywaj je, aby używać ich w wielu komponentach składanych. Możesz po prostu zapisać modyfikator, ponieważ jest to obiekt podobny do danych:

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

Wyodrębnianie i ponowne wykorzystywanie modyfikatorów przy obserwowaniu często zmieniających się stanów

Gdy obserwujemy często zmieniające się stany w komponowalnych elementach, takich jak stany animacji lub scrollState, może to powodować znaczną liczbę ponownych składań. W takim przypadku modyfikatory zostaną przypisane do każdej rekompozycji i potencjalnie do każdego kadru:

@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 wykorzystać ponownie takie samo wystąpienie modyfikatora oraz przekazać je 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 niesklasyfikowanych modyfikatorów

Modyfikatory mogą być nieograniczone lub ograniczone do konkretnego kompozytowanego elementu. W przypadku modyfikatorów bez zakresu możesz je łatwo wyodrębnić spoza kompozytowych 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 schematami opóźnionego ładowania. W większości przypadków warto, aby wszystkie potencjalnie istotne elementy miały dokładnie takie 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 ograniczonych

W przypadku modyfikatorów ograniczonych do określonych komponentów możesz wyodrębnić je na najwyższym możliwym poziomie i w odpowiednich przypadkach ponownie ich użyć:

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

Do bezpośrednich podrzędnych elementów o tym samym zakresie należy przekazywać tylko wyodrębnione modyfikatory zakresu. 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

Wyodrębnione ciągi modyfikatorów możesz dalej łączyć lub dołączać, 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 jednak, że kolejność modyfikatorów ma znaczenie.

Więcej informacji

Udostępniamy pełną listę modyfikatorów wraz z ich parametrami i zakresami.

Aby dowiedzieć się więcej o używaniu modyfikatorów, możesz też zapoznać się z materiałami o układach podstawowych w ćwiczeniach z programowania lub zapoznać się z repozytorium Now in Android.

Więcej informacji o modyfikatorach niestandardowych i sposobie ich tworzenia znajdziesz w dokumentacji Niestandardowe układy – korzystanie z modyfikatora układu.