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. kompozycji elementów,
  2. układu elementów,
  3. rysowania elementów.

Tworzenie stanu przekształcenia w interfejsie za pomocą kompozycji, układu i rysowania

Ten dokument skupia się na układzie elementów i wyjaśnia, jakie bloki konstrukcyjne udostępnia Compose, aby ułatwić Ci rozmieszczanie elementów interfejsu.

Cele układów w Compose

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

Podstawy funkcji typu „composable”

Funkcje typu „composable” to podstawowe elementy składowe Compose. Funkcja typu „composable” to funkcja emitująca Unit, która opisuje część interfejsu. Funkcja przyjmuje dane wejściowe i generuje to, co jest wyświetlane na ekranie. Więcej informacji o funkcjach typu „composable” znajdziesz w dokumentacji modelu mentalnego Compose.

Funkcja typu „composable” może emitować kilka elementów interfejsu. Jeśli jednak nie podasz wskazówek dotyczących ich rozmieszczenia, Compose może ułożyć elementy w sposób, który Ci się nie spodoba. 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, co sprawia, że są one nieczytelne:

Dwa elementy tekstowe narysowane jeden na drugim, przez co tekst jest nieczytelny

Compose udostępnia kolekcję gotowych układów, które ułatwiają rozmieszczanie elementów interfejsu, a także umożliwia łatwe definiowanie własnych, bardziej wyspecjalizowanych układów.

Standardowe komponenty układu

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

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

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

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

Podobnie użyj Row aby umieścić elementy poziomo na ekranie. 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)
        }
    }
}

Wyświetla 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 konkretnego 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")
    }
}

Wyświetla dwa elementy ułożone jeden na drugim.

Często te bloki konstrukcyjne wystarczą. Możesz napisać własną funkcję typu „composable”, aby połączyć te układy w bardziej złożony układ, który będzie pasować do Twojej aplikacji.

Porównanie 3 prostych komponentów układu: kolumny, wiersza i pola

Aby ustawić pozycję elementów podrzędnych w 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 rozmieszczane w jednym przebiegu. Każdy węzeł jest najpierw proszony o zmierzenie się, a następnie o zmierzenie wszystkich elementów podrzędnych rekurencyjnie, przekazując ograniczenia rozmiaru w dół drzewa do elementów podrzędnych. Następnie elementy liściowe są określane i umieszczane, a rozwiązane rozmiary i instrukcje umieszczania są przekazywane z powrotem w górę drzewa.

Krótko mówiąc, elementy nadrzędne mierzą się przed elementami podrzędnymi, ale są określane i umieszczane po elementach podrzędnych.

Rozważmy tę funkcję SearchResult.

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

Ta funkcja tworzy to drzewo interfejsu.

SearchResult
  Row
    Image
    Column
      Text
      Text

W przykładzie SearchResult układ drzewa interfejsu jest tworzony w tej kolejności:

  1. Węzeł główny Row jest proszony o zmierzenie się.
  2. Węzeł główny Row prosi swój pierwszy element podrzędny, Image, o zmierzenie się.
  3. Image jest węzłem liściowym (czyli nie ma elementów podrzędnych), więc zgłasza rozmiar i zwraca instrukcje umieszczania.
  4. Węzeł główny Row prosi swój drugi element podrzędny, Column, o zmierzenie się.
  5. Węzeł Column prosi swój pierwszy element podrzędny Text o zmierzenie się.
  6. Pierwszy węzeł Text jest węzłem liściowym, więc zgłasza rozmiar i zwraca instrukcje umieszczania.
  7. Węzeł Column prosi swój drugi element podrzędny Text o zmierzenie się.
  8. Drugi węzeł Text jest węzłem liściowym, więc zgłasza rozmiar i zwraca instrukcje umieszczania.
  9. Teraz, gdy węzeł Column zmierzył, określił i umieścił swoje elementy podrzędne, może określić swój rozmiar i położenie.
  10. Teraz, gdy węzeł główny Row zmierzył, określił 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 w jednym przebiegu jest korzystny dla wydajności, ponieważ umożliwia Compose wydajne obsługiwanie głębokich drzew interfejsu. Jeśli element zmierzyłby swój element podrzędny 2 razy, a ten element podrzędny zmierzyłby każdy ze swoich elementów podrzędnych 2 razy itd., pojedyncza próba rozmieszczenia całego interfejsu wymagałaby wykonania wielu czynności, co utrudniłoby utrzymanie wydajności aplikacji.

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

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

Używanie modyfikatorów w układach

Jak wspomnieliśmy w artykule Modyfikatory Compose, możesz używać modyfikatorów do dekorowania lub rozszerzania funkcji typu „composable”. 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),
        ) { /*...*/ }
    }
}

Jeszcze bardziej złożony układ, w którym modyfikatory zmieniają sposób rozmieszczenia grafiki i określają, które obszary reagują na dane wejściowe użytkownika.

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

  • clickable sprawia, że funkcja typu „composable” reaguje na dane wejściowe użytkownika i wyświetla efekt fali.
  • padding dodaje odstęp wokół elementu.
  • fillMaxWidth sprawia, że funkcja typu „composable” wypełnia maksymalną szerokość podaną 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 z możliwością przewijania znajdziesz w dokumentacji gestów Compose.

Więcej informacji o listach i listach leniwych znajdziesz w dokumentacji list Compose.

Elastyczne układy stron

Układ należy zaprojektować z uwzględnieniem różnych orientacji ekranu i rozmiarów formatu. Compose oferuje kilka gotowych mechanizmów, które ułatwiają dostosowywanie układów funkcji typu „composable” do różnych konfiguracji ekranu.

Ograniczenia

Aby poznać ograniczenia pochodzące od elementu nadrzędnego i odpowiednio zaprojektować układ, możesz użyć BoxWithConstraints. Ograniczenia pomiaru można znaleźć w zakresie lambdy treści. Możesz użyć tych ograniczeń pomiaru, aby tworzyć 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 slotach

Compose provides a large variety of composables based on Material Design with the androidx.compose.material:material dependency (included when creating a Compose project in Android Studio) to make UI building easy. Dostępne są elementy takie jak Drawer, FloatingActionButton, i TopAppBar.

Komponenty Material Design w dużym stopniu korzystają z interfejsów API slotów, czyli wzorca wprowadzonego przez Compose aby dodać warstwę dostosowywania do funkcji typu „composable”. Dzięki temu komponenty są bardziej elastyczne, ponieważ akceptują element podrzędny, który może się samodzielnie skonfigurować, zamiast udostępniać każdy parametr konfiguracji elementu podrzędnego. Sloty pozostawiają w interfejsie puste miejsce, które deweloper może wypełnić według własnego uznania. Oto na przykład sloty, które możesz dostosować w TopAppBar:

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

Funkcje typu „composable” zwykle przyjmują lambdę typu „composable” content ( content: @Composable () -> Unit). Interfejsy API slotów udostępniają wiele parametrów content do określonych zastosowań. Na przykład TopAppBar umożliwia podanie treści dla title, navigationIcon i actions.

Na przykład, Scaffold umożliwia implementowanie interfejsu z podstawową strukturą układu Material Design. Scaffoldudostępnia sloty dla najpopularniejszych komponentów Material Design najwyższego poziomu, takich jak TopAppBar, BottomAppBar, FloatingActionButton, i Drawer. Dzięki Scaffold łatwo jest zadbać o to, aby te komponenty były prawidłowo umieszczone i działały razem.

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

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