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. Skład elementów
  2. Układ elementów
  3. Rysowanie elementów

Przekształcanie stanu w interfejs za pomocą kompozycji, układu i rysowania

W tym dokumencie skupiamy się na układzie elementów i wyjaśniamy niektóre bloki konstrukcyjne, które Compose udostępnia, aby ułatwić Ci rozmieszczenie elementów interfejsu.

Cele układów w Compose

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

Podstawy funkcji typu „composable"

Funkcje kompozycyjne to podstawowe elementy składowe Compose. Funkcja kompozycyjna 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 komponentach znajdziesz w dokumentacji Model myślowy 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 nieodpowiedni sposób. 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 elementów funkcja Twórz układa elementy tekstowe jeden na drugim, przez co stają się one nieczytelne:

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

Compose udostępnia kolekcję gotowych układów, które pomagają rozmieszczać elementy interfejsu, a także 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 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 element Column, jak i Row umożliwiają 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 2 elementy ułożone jeden na drugim.

Często te elementy są wszystkim, czego potrzebujesz. 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ównuje 3 proste komponenty kompozycyjne układu: kolumnę, wiersz i pole.

Aby ustawić pozycję dzieci w ramach Row, ustaw argumenty horizontalArrangementverticalAlignment. W przypadku Column ustaw argumenty verticalArrangementhorizontalAlignment:

@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 samego siebie, 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 węzły liści są dopasowywane i umieszczane, a rozwiązane rozmiary i instrukcje dotyczące umieszczania są przekazywane z powrotem w górę drzewa.

Rodzice są mierzeni przed dziećmi, ale ich rozmiar i pozycja są określane po dzieciach.

Rozważmy tę funkcję SearchResult.

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

Ta funkcja generuje ten drzewo interfejsu:

SearchResult
  Row
    Image
    Column
      Text
      Text

SearchResult przykładzie układ drzewa interfejsu jest zgodny z tą kolejnością:

  1. Węzeł główny Row jest proszony o przeprowadzenie pomiaru.
  2. Węzeł główny Row prosi pierwszy element podrzędny, Image, o pomiar.
  3. Image jest węzłem liścia (czyli nie ma węzłów podrzędnych), więc zgłasza rozmiar i zwraca instrukcje dotyczące umieszczenia.
  4. Węzeł główny Row prosi drugi element podrzędny, Column, o pomiar.
  5. Węzeł Column prosi swój pierwszy węzeł podrzędny Text o przeprowadzenie pomiaru.
  6. Pierwszy węzeł Text jest węzłem-liściem, więc zgłasza rozmiar i zwraca instrukcje dotyczące miejsca docelowego.
  7. Węzeł Column prosi swój drugi węzeł podrzędny Text o dokonanie pomiaru.
  8. Drugi węzeł Text jest węzłem-liściem, więc zgłasza rozmiar i zwraca instrukcje dotyczące umieszczenia.
  9. Teraz, gdy węzeł Column zmierzył, określił rozmiar i umieścił swoje węzły 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ć własny rozmiar i położenie.

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

Wydajność

Compose osiąga wysoką skuteczność, ponieważ mierzy dzieci 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ł swój element podrzędny 2 razy, a ten element podrzędny zmierzył każdy ze swoich elementów podrzędnych 2 razy itd., pojedyncza próba rozmieszczenia całego interfejsu użytkownika wymagałaby wykonania wielu działań, co utrudniałoby utrzymanie wydajności aplikacji.

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

Pomiar i umieszczanie to odrębne podetapy fazy układu, więc wszelkie zmiany, które wpływają tylko na umieszczanie elementów, a nie na pomiar, można wprowadzać oddzielnie.

Używanie modyfikatorów w układach

Jak wspomnieliśmy w sekcji Modyfikatory funkcji kompozycyjnych, 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),
        ) { /*...*/ }
    }
}

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

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

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

Elastyczne układy stron

Układ powinien być zaprojektowany z uwzględnieniem różnych orientacji ekranu i rozmiarów urządzenia. 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 od elementu nadrzędnego i odpowiednio zaprojektować układ, możesz użyć BoxWithConstraints. Ograniczenia pomiaru znajdują się w zakresie funkcji lambda treści. Za pomocą tych ograniczeń pomiarowych możesz 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 przedziałach

Compose udostępnia wiele komponentów opartych na Material Design z zależnością androidx.compose.material:material (uwzględnioną podczas tworzenia projektu Compose w Android Studio), aby ułatwić tworzenie interfejsu. Dostępne są elementy takie jak Drawer, FloatingActionButtonTopAppBar.

Komponenty Material w dużej mierze korzystają z interfejsów API gniazd, czyli wzorca wprowadzonego przez Compose, aby dodać warstwę dostosowywania do funkcji kompozycyjnych. Dzięki temu komponenty są bardziej elastyczne, ponieważ akceptują element podrzędny, który może się konfigurować samodzielnie, zamiast udostępniać każdy parametr konfiguracji elementu podrzędnego. Sloty pozostawiają w interfejsie pustą przestrzeń, którą deweloper może wypełnić w dowolny sposób. Na przykład w TopAppBar możesz dostosować te miejsca:

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

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

Na przykład Scaffold umożliwia wdrożenie interfejsu z podstawową strukturą układu Material Design. Scaffoldzawiera miejsca na najpopularniejsze komponenty Material najwyższego poziomu, takie jak TopAppBar, BottomAppBar, FloatingActionButtonDrawer. Dzięki Scaffold możesz łatwo sprawdzić, czy te komponenty są prawidłowo rozmieszczone i czy ze sobą współpracują.

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

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