Podstawy układu tworzenia wiadomości

Jetpack Compose znacznie ułatwia projektowanie i tworzenie interfejsu aplikacji. Compose przekształca stan w elementy interfejsu za pomocą:

  1. Kompozycja elementów
  2. Układ elementów
  3. Rysunek elementów

Compose przekształca stan w interfejs za pomocą kompozycji, układu i rysowania

Ten dokument skupia się na układzie elementów i wyjaśnia niektóre elementy składowe, które Compose udostępnia, aby pomóc Ci w rozmieszczaniu elementów interfejsu.

Cele układów w Compose

Implementacja systemu układu w Jetpack Compose ma 2 główne cele:

Podstawowe informacje o funkcjach typu „composable”

Funkcje typu „composable” to podstawowe elementy składowe Compose. Funkcja typu „composable” to funkcja, która emituje Unit opisujący część interfejsu. Funkcja pobiera dane wejściowe i generuje to, co jest wyświetlane na ekranie. Więcej informacji o funkcjach kompozycyjnych znajdziesz w dokumentacji Model mentalny Compose.

Funkcja typu „composable” może emitować kilka elementów interfejsu. Jeśli jednak nie określisz, jak mają być rozmieszczone, Compose może je ułożyć w sposób, który Ci nie odpowiada. Na przykład ten kod generuje 2 elementy tekstowe:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Bez wskazówek dotyczących rozmieszczenia Compose układa elementy tekstowe jeden na drugim, przez co są nieczytelne:

Dwa elementy tekstowe narysowane jeden na drugim, co sprawia, że tekst jest nieczytelny

Compose udostępnia kolekcję gotowych układów, które ułatwiają rozmieszczanie elementów interfejsu, oraz ułatwia definiowanie własnych, bardziej wyspecjalizowanych układów.

Komponenty układu standardowego

W wielu przypadkach możesz po prostu użyć standardowych elementów układu Compose.

Użyj Column, aby umieścić elementy na ekranie w pionie.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

2 elementy tekstowe ułożone w kolumnie, dzięki czemu tekst jest czytelny

Podobnie możesz użyć Row, aby umieścić elementy na ekranie w poziomie. Zarówno Column, jak i Row obsługują konfigurowanie wyrównania elementów, które zawierają.

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Bardziej złożony układ z małą grafiką obok kolumny elementów tekstowych

Użyj Box, aby umieścić elementy jeden na drugim. Box obsługuje też konfigurowanie określonego wyrównania elementów, które zawiera.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Pokazuje 2 elementy ułożone jeden nad drugim

Często te elementy składowe są wszystkim, czego potrzebujesz. Możesz napisać własną funkcję typu „composable”, aby połączyć te układy w bardziej rozbudowany układ, który pasuje do Twojej aplikacji.

Porównuje 3 proste elementy kompozycyjne układu: kolumnę, wiersz i pole

Aby ustawić pozycję elementów podrzędnych w komponencie Row, ustaw argumenty horizontalArrangement i verticalAlignment. W przypadku Column ustaw argumenty verticalArrangement i horizontalAlignment:

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Elementy są wyrównane do prawej

Model układu

W modelu układu drzewo interfejsu jest układane w jednym przebiegu. Każdy węzeł jest najpierw proszony o zmierzenie siebie, a następnie o rekurencyjne zmierzenie wszystkich elementów podrzędnych, przekazując ograniczenia rozmiaru w dół drzewa do elementów podrzędnych. Następnie określane są rozmiary i położenie węzłów liści, a rozwiązane rozmiary i instrukcje dotyczące położenia są przekazywane z powrotem w górę drzewa.

Krótko mówiąc, elementy nadrzędne są mierzone przed elementami podrzędnymi, ale ich rozmiar i położenie są określane po elementach podrzędnych.

Rozważ następującą funkcję SearchResult.

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Ta funkcja generuje to drzewo interfejsu.

SearchResult
  Row
    Image
    Column
      Text
      Text

W przykładzie SearchResult układ drzewa UI ma taką kolejność:

  1. Węzeł główny Row jest proszony o pomiar.
  2. Węzeł główny Row prosi swój pierwszy element podrzędny Image o pomiar.
  3. Image jest węzłem liścia (czyli nie ma elementów podrzędnych), więc raportuje rozmiar i zwraca instrukcje dotyczące miejsca docelowego.
  4. Węzeł główny Row prosi o pomiar drugi element podrzędny, Column.
  5. Węzeł Column prosi swój pierwszy element podrzędny Text o pomiar.
  6. Pierwszy węzeł Text jest węzłem-liściem, więc raportuje rozmiar i zwraca instrukcje dotyczące miejsca docelowego.
  7. Węzeł Column prosi swój drugi węzeł podrzędny Text o pomiar.
  8. Drugi węzeł Text jest węzłem-liściem, więc raportuje rozmiar i zwraca instrukcje dotyczące miejsca docelowego.
  9. Teraz, gdy węzeł Column zmierzył, określił rozmiar i umieścił swoje elementy podrzędne, może określić własny rozmiar i położenie.
  10. Teraz, gdy węzeł główny Row zmierzył, określił rozmiar i umieścił swoje elementy podrzędne, może określić swój rozmiar i położenie.

Kolejność pomiaru, określania rozmiaru i umieszczania w drzewie UI wyników wyszukiwania

Wydajność

Compose osiąga wysoką wydajność, mierząc elementy podrzędne tylko raz. Pomiar jednoprzebiegowy jest korzystny dla wydajności, ponieważ umożliwia Compose efektywne obsługiwanie głębokich drzew interfejsu. Jeśli element mierzy swój element podrzędny dwukrotnie, a ten element podrzędny mierzy każdy ze swoich elementów podrzędnych dwukrotnie itd., pojedyncza próba ułożenia całego interfejsu wymagałaby dużo pracy, co utrudniłoby utrzymanie wydajności aplikacji.

Jeśli z jakiegoś powodu układ wymaga wielu pomiarów, Compose oferuje specjalny system pomiarów wewnętrznych. Więcej informacji o tej funkcji znajdziesz w artykule Pomiary wewnętrzne w układach Compose.

Ponieważ pomiar i umieszczanie to odrębne podetapy etapu układu, wszelkie zmiany, które wpływają tylko na umieszczanie elementów, a nie na pomiar, można wykonywać osobno.

Używanie modyfikatorów w układach

Jak opisano w artykule Modyfikatory w Compose, możesz używać modyfikatorów do dekorowania lub rozszerzania funkcji kompozycyjnych. Modyfikatory są niezbędne do dostosowywania układu. Na przykład tutaj łączymy kilka modyfikatorów, aby dostosować ArtistCard:

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

Bardziej złożony układ, w którym modyfikatory zmieniają sposób rozmieszczenia grafiki i obszary reagujące na działania użytkownika.

W kodzie powyżej zwróć uwagę na różne funkcje modyfikatora używane razem.

  • clickable sprawia, że element kompozycyjny reaguje na dane wejściowe użytkownika i wyświetla efekt fali.
  • padding dodaje odstęp wokół elementu.
  • fillMaxWidth sprawia, że element kompozycyjny wypełnia maksymalną szerokość nadaną mu przez element nadrzędny.
  • size() określa preferowaną szerokość i wysokość elementu.

Układy z możliwością przewijania

Więcej informacji o układach przewijanych znajdziesz w dokumentacji dotyczącej gestów w Compose.

Informacje o listach i listach leniwych znajdziesz w dokumentacji dotyczącej list w Compose.

Elastyczne układy stron

Układ powinien być zaprojektowany z uwzględnieniem różnych orientacji ekranu i rozmiarów urządzeń. Compose oferuje kilka gotowych mechanizmów, które ułatwiają dostosowywanie układów kompozycyjnych do różnych konfiguracji ekranu.

Ograniczenia

Aby poznać ograniczenia pochodzące z elementu nadrzędnego i odpowiednio zaprojektować układ, możesz użyć BoxWithConstraints. Ograniczenia pomiaru można znaleźć w zakresie funkcji lambda treści. Możesz użyć tych ograniczeń pomiaru, aby utworzyć różne układy dla różnych konfiguracji ekranu:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Układy oparte na boksach

Compose udostępnia wiele różnych funkcji kompozycyjnych opartych na Material Design z zależnością androidx.compose.material:material (uwzględnianą podczas tworzenia projektu Compose w Android Studio), co ułatwia tworzenie interfejsu. Dostępne są takie elementy jak Drawer, FloatingActionButton i TopAppBar.

Komponenty Material Design intensywnie korzystają z interfejsów API gniazd, czyli wzorca wprowadzonego przez Compose, który umożliwia dostosowywanie funkcji kompozycyjnych. Dzięki temu komponenty są bardziej elastyczne, ponieważ akceptują element podrzędny, który może się sam skonfigurować, zamiast ujawniać każdy parametr konfiguracji elementu podrzędnego. Miejsca pozostawiają puste miejsce w interfejsie, które deweloper może wypełnić według własnego uznania. Oto przykładowe boksy, które możesz dostosować w TopAppBar:

Diagram przedstawiający dostępne miejsca na pasku aplikacji Material Components

Komponenty zazwyczaj przyjmują funkcję kompozycyjną lambda content ( content: @Composable () -> Unit). Interfejsy API gniazd udostępniają wiele parametrów content do konkretnych zastosowań. Na przykład TopAppBar umożliwia podanie treści dla title, navigationIcon i actions.

Na przykład Scaffold umożliwia wdrożenie interfejsu z podstawową strukturą układu Material Design. Scaffold udostępnia miejsca na najpopularniejsze komponenty Material najwyższego poziomu, takie jak TopAppBar, BottomAppBar, FloatingActionButton i Drawer. Dzięki Scaffold możesz łatwo upewnić się, że te komponenty są prawidłowo rozmieszczone i właściwie ze sobą współpracują.

Przykładowa aplikacja JetNews, która używa Scaffold do pozycjonowania wielu elementów

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}