Jetpack Compose znacznie ułatwia projektowanie i tworzenie interfejsu aplikacji. Compose przekształca stan w elementy interfejsu za pomocą:
- Skład elementów
- Układ elementów
- Rysowanie elementów
W tym dokumencie skupiamy się na układzie elementów i wyjaśniamy niektóre bloki konstrukcyjne, które udostępnia Compose, aby ułatwić Ci rozmieszczanie elementów interfejsu.
Cele układów w Compose
Implementacja systemu układu w Jetpack Compose ma 2 główne cele:
- Wysoka wydajność
- Możliwość łatwego tworzenia niestandardowych układów
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 funkcjach kompozycyjnych znajdziesz w dokumentacji Model myślowy Compose.
Funkcja kompozycyjna 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 elementów funkcja Compose układa elementy tekstowe jeden na drugim, przez co stają się one nieczytelne:
Compose udostępnia kolekcję gotowych układów, które pomagają w rozmieszczaniu elementów interfejsu, a także ułatwia definiowanie własnych, bardziej wyspecjalizowanych układów.
Komponenty standardowego układu
W wielu przypadkach wystarczy użyć standardowych elementów układu Compose.
Użyj elementu
Column
, aby umieścić elementy pionowo na ekranie.
@Composable fun ArtistCardColumn() { Column { Text("Alfred Sisley") Text("3 minutes ago") } }
Podobnie użyj
Row
do umieszczenia elementów w poziomie na ekranie. Zarówno 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) } } }
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") } }
Często te elementy są wszystkim, czego potrzebujesz. Możesz napisać własną funkcję kompozycyjną, aby połączyć te układy w bardziej złożony układ, który będzie pasować do Twojej aplikacji.
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 { /*...*/ } } }
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 potem 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 umieszczania są przekazywane z powrotem w górę drzewa.
Rodzice mierzą się przed dziećmi, ale są umieszczani za nimi.
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
W SearchResult
przykładzie układ drzewa interfejsu jest zgodny z tą kolejnością:
- Węzeł główny
Row
jest proszony o dokonanie pomiaru. - Węzeł główny
Row
prosi swój pierwszy węzeł podrzędny,Image
, o przeprowadzenie pomiaru. 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.- Węzeł główny
Row
prosi swoje drugie dziecko, węzełColumn
, o dokonanie pomiaru. - Węzeł
Column
prosi pierwszy element podrzędnyText
o przeprowadzenie pomiaru. - Pierwszy węzeł
Text
jest węzłem-liściem, więc zgłasza rozmiar i zwraca instrukcje dotyczące umieszczenia. - Węzeł
Column
prosi drugie dzieckoText
o dokonanie pomiaru. - Drugi węzeł
Text
jest węzłem liścia, więc zgłasza rozmiar i zwraca instrukcje dotyczące umieszczenia. - 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. - Gdy węzeł główny
Row
zmierzy, określi rozmiar i umieści swoje węzły podrzędne, może określić własny rozmiar i położenie.
Wydajność
Compose osiąga wysoką skuteczność, ponieważ mierzy dzieci tylko raz. Pomiar jednoprzebiegowy jest korzystny dla wydajności, ponieważ umożliwia Compose efektywne obsługiwanie rozbudowanych 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 z jakiegoś powodu wymaga 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 procesu 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), ) { /*...*/ } } }
W powyższym kodzie zwróć uwagę na różne funkcje modyfikujące użyte razem.
clickable
reaguje na działania użytkownika i wyświetla efekt fali.padding
dodaje odstępy wokół elementu.fillMaxWidth
sprawia, że komponent kompozycyjny wypełnia 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 list w Compose.
Elastyczne układy
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
, FloatingActionButton
i TopAppBar
.
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:
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
, navigationIcon
i actions
.
Na przykład Scaffold
umożliwia wdrożenie interfejsu z podstawową strukturą układu Material Design.
Scaffold
zawiera miejsca na najpopularniejsze komponenty Material najwyższego poziomu, takie jak TopAppBar
, BottomAppBar
, FloatingActionButton
i Drawer
. Dzięki użyciu komponentu Scaffold
możesz łatwo sprawdzić, czy te komponenty są prawidłowo rozmieszczone i czy działają ze sobą prawidłowo.
@Composable fun HomeScreen(/*...*/) { ModalNavigationDrawer(drawerContent = { /* ... */ }) { Scaffold( topBar = { /*...*/ } ) { contentPadding -> // ... } } }
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony.
- Tworzenie modyfikatorów
- Kotlin w Jetpack Compose
- Komponenty i układy Material