Jetpack Compose znacznie ułatwia projektowanie i tworzenie interfejsu aplikacji. Compose przekształca stan w elementy interfejsu za pomocą tych mechanizmów:
- Kompozycja elementów
- Układ elementów
- Rysowanie elementów
Ten dokument skupia się na układzie elementów i wyjaśnia, jakie bloki składowe udostępnia Compose, aby ułatwić Ci tworzenie układów elementów interfejsu.
Cele układów w Compose
Implementacja systemu układów w Jetpack Compose ma 2 główne cele:
- wysoka wydajność,
- możliwość łatwego pisania niestandardowych układów
Podstawy funkcji typu „composable”
Funkcje typu „composable” to podstawowe bloki 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 utrudnia ich odczytanie:
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") } }
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) } } }
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 bloki składowe 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.
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 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 węzły liści są rozmiarowane 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ą rozmiarowane 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:
- Węzeł główny
Rowjest proszony o zmierzenie się. - Węzeł główny
Rowprosi swój pierwszy element podrzędny,Image, o zmierzenie się. Imageto węzeł liścia (czyli nie ma elementów podrzędnych), więc zgłasza rozmiar i zwraca instrukcje umieszczania.- Węzeł główny
Rowprosi swój drugi element podrzędny,Column, o zmierzenie się. - Węzeł
Columnprosi swój pierwszy element podrzędnyTexto zmierzenie się. - Pierwszy węzeł
Textjest węzłem liścia, więc zgłasza rozmiar i zwraca instrukcje umieszczania. - Węzeł
Columnprosi swój drugi element podrzędnyTexto zmierzenie się. - Drugi węzeł
Textjest węzłem liścia, więc zgłasza rozmiar i zwraca instrukcje umieszczania. - Teraz, gdy węzeł
Columnzmierzył, rozmiarował i umieścił swoje elementy podrzędne, może określić swój rozmiar i położenie. - Teraz, gdy węzeł główny
Rowzmierzył, rozmiarował i umieścił swoje elementy podrzędne, może określić swój rozmiar i położenie.
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 ułożenia całego interfejsu wymagałaby dużo pracy, co utrudniał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 Pomiar wewnętrzny w układach Compose.
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), ) { /*...*/ } } }
W powyższym kodzie zwróć uwagę na różne funkcje modyfikatorów używane razem.
clickablesprawia, że funkcja typu „composable” reaguje na dane wejściowe użytkownika i wyświetla efekt fali.paddingdodaje odstęp wokół elementu.fillMaxWidthsprawia, ż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.
W przypadku list i list leniwych zapoznaj się z dokumentacją list Compose.
Elastyczne układy stron
Układ powinien być zaprojektowany z uwzględnieniem różnych orientacji ekranu i rozmiarów formatu. Compose oferuje kilka 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:
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.
@Composable fun HomeScreen(/*...*/) { ModalNavigationDrawer(drawerContent = { /* ... */ }) { Scaffold( topBar = { /*...*/ } ) { contentPadding -> // ... } } }
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Modyfikatory Compose
- Kotlin dla Jetpack Compose
- Komponenty i układy Material Design