1. Wprowadzenie
Z tego ćwiczenia w Codelabs dowiesz się, jak tworzyć aplikacje adaptacyjne na telefony, tablety i urządzenia składane, a także jak zwiększyć osiągalność w Jetpack Compose. Poznasz też sprawdzone metody korzystania z komponentów i motywów Material 3.
Zanim przejdziemy do szczegółów, musimy zrozumieć, co rozumiemy przez adaptację.
Dostosowanie
Interfejs aplikacji powinien dostosowywać się do różnych rozmiarów okien, orientacji i formatów. Układ adaptacyjny zmienia się w zależności od dostępnego miejsca na ekranie. Mogą to być różne zmiany układu, od prostych korekt układu, przez wypełnianie przestrzeni, po wybór odpowiednich stylów nawigacji, aż po całkowitą zmianę układu w celu wykorzystania dodatkowego miejsca.
Więcej informacji znajdziesz w artykule Projektowanie adaptacyjne.
Z tego ćwiczenia w Codelabs dowiesz się, jak używać Jetpack Compose i myśleć o adaptacji. Tworzysz aplikację o nazwie Reply, w której pokazujesz, jak wdrożyć adaptację do różnych urządzeń i jak możliwości adaptacji i osiągania współpracują ze sobą, by zapewnić użytkownikom optymalne wrażenia.
Czego się nauczysz
- Jak zaprojektować aplikację tak, aby kierowała reklamy na wszystkie rozmiary okien w Jetpack Compose.
- Jak kierować aplikację na różne urządzenia składane.
- Jak korzystać z różnych typów nawigacji, aby zwiększyć dostępność i ułatwienia dostępu.
- Jak używać komponentów Material 3, aby zapewnić najlepszą jakość obrazu dla każdego rozmiaru okna.
Czego potrzebujesz
- Najnowsza stabilna wersja Android Studio.
- Urządzenie wirtualne z Androidem 13 z możliwością zmiany rozmiaru.
- Wiedza na temat Kotlina
- Podstawowa znajomość funkcji tworzenia wiadomości (np. adnotacja
@Composable
). - Podstawowa znajomość układów tworzenia wiadomości (np.
Row
iColumn
). - Podstawowa znajomość modyfikatorów (np.
Modifier.padding()
).
W tym ćwiczeniu w programowaniu użyj emulatora z możliwością zmiany rozmiaru, który pozwala przełączać się między różnymi typami urządzeń i rozmiarami okien.
Jeśli nie znasz funkcji Compose, przed ukończeniem tego ćwiczenia z programowania możesz ukończyć podstawowe ćwiczenia z programowania w Jetpack Compose.
Co utworzysz
- Interaktywna aplikacja klienta poczty e-mail o nazwie Reply, która korzysta ze sprawdzonych metod dostosowywania projektów, różnych opcji nawigacji w stylu Material Design i optymalnego wykorzystania miejsca na ekranie.
2. Konfiguracja
Aby pobrać kod do tego ćwiczenia z programowania, skopiuj repozytorium GitHub za pomocą wiersza poleceń:
git clone https://github.com/android/codelab-android-compose.git cd codelab-android-compose/AdaptiveUiCodelab
Możesz też pobrać repozytorium jako plik ZIP:
Warto zacząć od kodu w gałęzi main i wykonywać ćwiczenia z programowania krok po kroku we własnym tempie.
Otwórz projekt w Android Studio
- W oknie Welcome to Android Studio (Witamy w Android Studio) wybierz
Otwórz istniejący projekt.
- Wybierz folder
<Download Location>/AdaptiveUiCodelab
(pamiętaj, by wybrać katalogAdaptiveUiCodelab
zawierający folderbuild.gradle
). - Gdy Android Studio zaimportuje projekt, sprawdź, czy możesz uruchomić gałąź
main
.
Poznawanie kodu startowego
Główny kod oddziału zawiera pakiet ui
. W tym pakiecie będziesz pracować z tymi plikami:
MainActivity.kt
– aktywność związana z punktem wejścia, w którym uruchamiasz aplikację.ReplyApp.kt
– zawiera elementy kompozycyjne interfejsu użytkownika na ekranie głównym.ReplyHomeViewModel.kt
– podaje dane i stan UI dotyczący zawartości aplikacji.ReplyListContent.kt
– zawiera elementy kompozycyjne służące do udostępniania list i ekranów z informacjami.
Jeśli uruchomisz tę aplikację na emulatorze z możliwością zmiany rozmiaru i wypróbujesz różne typy urządzeń, takie jak telefon czy tablet, interfejs po prostu rozwinie się do danej przestrzeni, nie wykorzystując powierzchni na ekranie czy zapewniając ergonomię osiągalności.
Zaktualizujesz ją, aby wykorzystać miejsce na ekranie, zwiększyć łatwość obsługi i poprawić ogólne wrażenia użytkowników.
3. Dostosowywanie aplikacji
W tej sekcji omawiamy, co oznacza dostosowywanie aplikacji i jakie komponenty zapewnia Material 3, aby to ułatwić. Dotyczy to też typów ekranów i stanów, na które kierujesz reklamy, w tym telefonów, tabletów, dużych tabletów i urządzeń składanych.
Na początek przyjrzymy się podstawom dotyczącym rozmiarów okien, stanu po przewinięciu i różnych opcji nawigacji. Następnie możesz użyć tych interfejsów API, aby dostosować aplikację do własnych potrzeb.
Rozmiary okien
Urządzenia z Androidem są dostępne we wszystkich kształtach i rozmiarach – od telefonów, przez składane, po tablety i urządzenia z ChromeOS. Aby obsługiwać jak najwięcej rozmiarów okien, interfejs musi być elastyczny i adaptacyjny. Aby pomóc Ci określić właściwy próg, po którym musisz zmienić interfejs aplikacji, zdefiniowaliśmy wartości punktów przerwania, które pomagają klasyfikować urządzenia do wstępnie zdefiniowanych klas rozmiarów (kompaktowych, średnich i rozwiniętych), nazywanych klasami rozmiaru okna. To zestaw sprawdzonych punktów przerwania widocznego obszaru, które pomogą Ci projektować, tworzyć i testować elastyczne i adaptacyjne układy aplikacji.
Kategorie zostały wybrane specjalnie z myślą o prostocie układu i elastyczności, która pozwala zoptymalizować aplikację pod kątem unikalnych przypadków. Klasa rozmiaru okna jest zawsze określana na podstawie przestrzeni ekranu dostępnej dla aplikacji, która nie musi pokrywać się z pełnym ekranem na potrzeby wielozadaniowości lub innych podziałów na segmenty.
Zarówno szerokość, jak i wysokość są klasyfikowane oddzielnie, więc w każdej chwili aplikacja ma 2 klasy rozmiaru okna – jedną dla szerokości i jedną dla wysokości. Dostępna szerokość jest zwykle ważniejsza niż wysokość ze względu na powszechność przewijania w pionie, więc w tym przypadku należy również użyć klas rozmiaru szerokości.
Stany zwijania
Urządzenia składane oferują jeszcze więcej sytuacji, do których aplikacja może dostosować się dzięki różnym rozmiarom i możliwością zawiasów. Zawiasy mogą zasłonić część wyświetlacza, przez co nie nadaje się do wyświetlania treści. mogą się też rozdzielić, co oznacza, że po rozłożeniu urządzenia urządzenie ma 2 osobne wyświetlacze.
Poza tym, gdy zawias jest częściowo otwarty, użytkownik może patrzeć na wewnętrzny wyświetlacz. W rezultacie w zależności od orientacji urządzenia może on mieć różne ustawienia fizyczne: na stole (położenie w poziomie – na zdjęciu powyżej) i położenie książki (zawinięcie w pionie).
Dowiedz się więcej o ustawieniach i zawiasach po złożeniu.
Wszystkie te kwestie należy wziąć pod uwagę przy wdrażaniu układów adaptacyjnych, które obsługują urządzenia składane.
Uzyskaj informacje adaptacyjne
Biblioteka Material3 adaptive
zapewnia wygodny dostęp do informacji o oknie, w którym działa Twoja aplikacja.
- Dodaj wpisy dotyczące tego artefaktu i jego wersji do pliku katalogu wersji:
gradle/libs.versions.toml
[versions]
material3Adaptive = "1.0.0-beta01"
[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
- W pliku kompilacji modułu aplikacji dodaj nową zależność biblioteki, a następnie przeprowadź synchronizację Gradle:
app/build.gradle.kts
dependencies {
implementation(libs.androidx.material3.adaptive)
}
Teraz w każdym zakresie kompozycyjnym możesz użyć narzędzia currentWindowAdaptiveInfo()
, aby uzyskać obiekt WindowAdaptiveInfo
zawierający informacje takie jak bieżąca klasa rozmiaru okna i informacje o tym, czy urządzenie jest w stanie złożonym, takim jak stan stołu.
Możesz to teraz wypróbować w aplikacji MainActivity
.
- W polu
onCreate()
w blokuReplyTheme
uzyskaj informacje o adaptacyjnym oknie i wyświetl klasy rozmiaru w funkcji kompozycyjnejText
. Możesz dodać ten kod po elemencieReplyApp()
:
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ReplyTheme {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
ReplyApp(
replyHomeUIState = uiState,
onEmailClick = viewModel::setSelectedEmail
)
val adaptiveInfo = currentWindowAdaptiveInfo()
val sizeClassText =
"${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
"${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
Text(
text = sizeClassText,
color = Color.Magenta,
modifier = Modifier.padding(
WindowInsets.safeDrawing.asPaddingValues()
)
)
}
}
}
Po uruchomieniu aplikacji wyświetlą się klasy rozmiaru okna wydrukowane nad treścią aplikacji. Możesz sprawdzić, co jeszcze jest dostępne w informacjach adaptacyjnych dotyczących okna. Potem możesz usunąć ten element (Text
), ponieważ zakrywa on zawartość aplikacji i nie będzie potrzebny w kolejnych krokach.
4. Dynamiczna nawigacja
Aby ułatwić sobie korzystanie z aplikacji, dostosuj jej położenie do zmian stanu i rozmiaru urządzenia.
Gdy użytkownik trzyma telefon, jego palce zazwyczaj znajdują się u dołu ekranu. Gdy użytkownik trzyma otwarte urządzenie składane lub tablet, jego palce zazwyczaj znajdują się blisko boków. Użytkownicy powinni mieć możliwość poruszania się po aplikacji lub inicjowania interakcji z nią bez konieczności utrzymywania skrajnych pozycji rąk ani zmieniania pozycji rąk.
Projektując aplikację i decydując, gdzie umieścić interaktywne elementy interfejsu w układzie, weź pod uwagę wpływ ergonomii na różne obszary ekranu.
- Wskaż obszary, w których możesz wygodnie sięgać, trzymając urządzenie.
- Do których obszarów można dotrzeć jedynie przez rozłożenie palców, co może być niewygodne?
- Do jakich obszarów trudno jest dotrzeć lub które są oddalone od miejsca, w którym użytkownik trzyma urządzenie?
Nawigacja jest pierwszą rzeczą, z którą użytkownicy wchodzą w interakcję. Obejmuje ona ważne działania związane z najważniejszymi ścieżkami użytkownika. Należy ją więc umieścić w miejscach, w których jest łatwo dotrzeć. Biblioteka adaptacyjna Material ma kilka komponentów, które pomagają wdrożyć nawigację w zależności od klasy rozmiaru okna urządzenia.
Dolna nawigacja
Dolna nawigacja sprawdza się idealnie w przypadku kompaktowych urządzeń, ponieważ kciukiem w naturalny sposób trzyma się urządzenie w miejscu, w którym kciuk może łatwo dotrzeć do wszystkich dolnych punktów nawigacji. Używaj, gdy Twoje urządzenie jest kompaktowe lub składane.
Kolumna nawigacji
W przypadku okna o średniej szerokości kolumna nawigacji jest idealna, jeśli chodzi o zasięg, ponieważ kciuk naturalnie umieszcza się z boku urządzenia. Możesz też połączyć ją z panelem nawigacji, by wyświetlać więcej informacji.
Panel nawigacji
Panel nawigacji pozwala w łatwy sposób przeglądać szczegółowe informacje o kartach nawigacyjnych. Jest on też łatwo dostępny, gdy korzystasz z tabletów lub większych urządzeń. Dostępne są 2 rodzaje szuflad nawigacji: modalny i stały.
Panel nawigacji modowej
W przypadku kompaktowych i średnich telefonów i tabletów można użyć modalnego panelu nawigacji, który można rozwinąć lub ukryć jako nakładka na treści. Czasem może ona być połączona z szynami nawigacyjnymi.
Stały panel nawigacji
Możesz użyć stałej szuflady nawigacji, aby zapewnić stałą nawigację na dużych tabletach, Chromebookach i komputerach.
Implementacja dynamicznej nawigacji
Teraz możesz przełączać się między różnymi typami nawigacji w zależności od stanu i rozmiaru urządzenia.
Obecnie pod treścią ekranu aplikacja zawsze wyświetla ikonę NavigationBar
niezależnie od stanu urządzenia. Zamiast tego możesz użyć komponentu Materiał NavigationSuiteScaffold
, aby automatycznie przełączać się między różnymi komponentami nawigacji na podstawie informacji takich jak bieżąca klasa rozmiaru okna.
- Dodaj zależność Gradle, aby pobrać ten komponent przez zaktualizowanie katalogu wersji i skryptu kompilacji aplikacji, a następnie przeprowadź synchronizację Gradle:
gradle/libs.versions.toml
[versions]
material3AdaptiveNavSuite = "1.3.0-beta01"
[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }
app/build.gradle.kts
dependencies {
implementation(libs.androidx.material3.adaptive.navigation.suite)
}
- Znajdź funkcję kompozycyjną
ReplyNavigationWrapper()
wReplyApp.kt
i zastąp elementColumn
i jego zawartośćNavigationSuiteScaffold
:
ReplyApp.kt
@Composable
private fun ReplyNavigationWrapperUI(
content: @Composable () -> Unit = {}
) {
var selectedDestination: ReplyDestination by remember {
mutableStateOf(ReplyDestination.Inbox)
}
NavigationSuiteScaffold(
navigationSuiteItems = {
ReplyDestination.entries.forEach {
item(
selected = it == selectedDestination,
onClick = { /*TODO update selection*/ },
icon = {
Icon(
imageVector = it.icon,
contentDescription = stringResource(it.labelRes)
)
},
label = {
Text(text = stringResource(it.labelRes))
},
)
}
}
) {
content()
}
}
Argument navigationSuiteItems
to blok, który umożliwia dodawanie elementów za pomocą funkcji item()
, podobnie jak dodawanie elementów w LazyColumn
. W końcowej funkcji lambda ten kod wywołuje metodę content()
przekazaną jako argument funkcji ReplyNavigationWrapperUI()
.
Uruchom aplikację w emulatorze i spróbuj zmienić rozmiar między telefonem, urządzeniem składanym i tabletem. Pasek nawigacyjny zmieni się na pasek nawigacyjny i z powrotem.
W przypadku bardzo szerokich okien, na przykład na tablecie w orientacji poziomej, dobrze jest pokazywać panel nawigacji. NavigationSuiteScaffold
umożliwia wyświetlanie stałego panelu, jednak nie jest on wyświetlany w żadnej z bieżących wartości WindowWidthSizeClass
. Możesz to jednak zrobić, wprowadzając niewielką zmianę.
- Dodaj następujący kod tuż przed połączeniem z numerem
NavigationSuiteScaffold
:
ReplyApp.kt
@Composable
private fun ReplyNavigationWrapperUI(
content: @Composable () -> Unit = {}
) {
var selectedDestination: ReplyDestination by remember {
mutableStateOf(ReplyDestination.Inbox)
}
val windowSize = with(LocalDensity.current) {
currentWindowSize().toSize().toDpSize()
}
val layoutType = if (windowSize.width >= 1200.dp) {
NavigationSuiteType.NavigationDrawer
} else {
NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
currentWindowAdaptiveInfo()
)
}
NavigationSuiteScaffold(
layoutType = layoutType,
...
) {
content()
}
}
Ten kod najpierw pobiera rozmiar okna i konwertuje go na jednostki DP za pomocą elementów currentWindowSize()
i LocalDensity.current
, a potem porównuje szerokość okna, aby wybrać typ układu interfejsu nawigacyjnego. Jeśli szerokość okna wynosi co najmniej 1200.dp
, używane jest NavigationSuiteType.NavigationDrawer
. W przeciwnym razie przywraca wartość domyślną.
Gdy ponownie uruchomisz aplikację na emulatorze z możliwością zmiany rozmiaru i wypróbujesz różne typy, zwróć uwagę, że po każdej zmianie konfiguracji ekranu lub rozłożeniu urządzenia składanego nawigacja zmienia się na odpowiedni dla tego rozmiaru.
Gratulacje, znasz już różne sposoby nawigacji dla różnych rozmiarów i stanów okien.
W następnej sekcji pokażemy, jak wykorzystać pozostały obszar ekranu, zamiast rozciągać cały obszar od krawędzi do krawędzi tego samego elementu listy.
5. Wykorzystanie miejsca na ekranie
Ekran zostanie rozciągnięty, aby zapełnić pozostałe miejsce niezależnie od tego, czy używasz go na małym, rozłożonym urządzeniu czy dużym tablecie. Chcesz wykorzystać tę przestrzeń na ekranie, aby wyświetlać więcej informacji, np. o tej aplikacji, oraz e-maile i wątki użytkownikom na tej samej stronie.
Material 3 definiuje 3 układy kanoniczne, z których każdy ma konfigurację klasy rozmiaru okna kompaktowego, średniego i rozwiniętego. W tym przypadku idealnie nadaje się układ kanoniczny Szczegóły listy. Jest on dostępny podczas tworzenia wiadomości jako ListDetailPaneScaffold
.
- Aby pobrać ten komponent, dodaj te zależności i przeprowadź synchronizację Gradle:
gradle/libs.versions.toml
[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }
app/build.gradle.kts
dependencies {
implementation(libs.androidx.material3.adaptive.layout)
implementation(libs.androidx.material3.adaptive.navigation)
}
- Znajdź funkcję kompozycyjną
ReplyAppContent()
w komórceReplyApp.kt
, która obecnie wyświetla tylko panel listy, wywołując funkcjęReplyListPane()
. Zastąp tę implementację implementacjąListDetailPaneScaffold
, wstawiając ten kod. To jest eksperymentalny interfejs API, więc musisz też dodać adnotację@OptIn
do funkcjiReplyAppContent()
:
ReplyApp.kt
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
replyHomeUIState: ReplyHomeUIState,
onEmailClick: (Email) -> Unit,
) {
val navigator = rememberListDetailPaneScaffoldNavigator<Long>()
ListDetailPaneScaffold(
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
listPane = {
ReplyListPane(replyHomeUIState, onEmailClick)
},
detailPane = {
ReplyDetailPane(replyHomeUIState.emails.first())
}
)
}
Ten kod najpierw tworzy nawigator za pomocą funkcji rememberListDetailPaneNavigator
. Nawigator zapewnia pewną kontrolę nad tym, który panel ma być wyświetlany i jaka zawartość ma się w nim znajdować. Omówię to później.
Gdy klasa rozmiaru okna jest rozwinięta, ListDetailPaneScaffold
wyświetla 2 panele. W przeciwnym razie jeden lub drugi panel wyświetla się na podstawie wartości podanych dla 2 parametrów: dyrektywy scaffold i wartości scaffold. Aby uzyskać domyślne działanie, ten kod wykorzystuje dyrektywę scaffold i wartość scaffold dostarczaną przez nawigator.
Pozostałe wymagane parametry to składowe lambda dla paneli. Listy ReplyListPane()
i ReplyDetailPane()
(dostępne w języku ReplyListContent.kt
) służą do wypełniania ról odpowiednio okien listy i szczegółów. Funkcja ReplyDetailPane()
oczekuje argumentu e-mail, więc na razie ten kod używa pierwszego adresu e-mail z listy adresów e-mail w usłudze ReplyHomeUIState
.
Uruchom aplikację i przełącz widok emulatora na składany lub tablet (może być też konieczna zmiana orientacji), aby zobaczyć układ z dwoma panelami. Już wygląda znacznie lepiej niż wcześniej!
Teraz przyjrzyjmy się niektórym elementom, które mogą być przydatne na tym ekranie. Gdy użytkownik kliknie e-maila na liście, powinien się on wyświetlić w okienku szczegółów wraz ze wszystkimi odpowiedziami. Obecnie aplikacja nie śledzi, który adres e-mail został wybrany, a kliknięcie elementu nic nie robi. Te informacje najlepiej przechowywać w pozostałych elementach interfejsu w interfejsie ReplyHomeUIState
.
- Otwórz
ReplyHomeViewModel.kt
i znajdź klasę danychReplyHomeUIState
. Dodaj właściwość do wybranego adresu e-mail z wartością domyślnąnull
:
ReplyHomeViewModel.kt
data class ReplyHomeUIState(
val emails : List<Email> = emptyList(),
val selectedEmail: Email? = null,
val loading: Boolean = false,
val error: String? = null
)
- W tym samym pliku funkcja
ReplyHomeViewModel
zawiera funkcjęsetSelectedEmail()
, która jest wywoływana, gdy użytkownik kliknie element listy. Zmodyfikuj tę funkcję, aby skopiować stan UI i zarejestrować wybrany e-mail:
ReplyHomeViewModel.kt
fun setSelectedEmail(email: Email) {
_uiState.update {
it.copy(selectedEmail = email)
}
}
Należy wziąć pod uwagę to, co się dzieje, zanim użytkownik kliknie dowolny element, a wybrany adres e-mail to null
. Co powinno być wyświetlane w panelu szczegółów? Można to zrobić na kilka sposobów, np. domyślnie wyświetlić pierwszą pozycję na liście.
- W tym samym pliku zmodyfikuj funkcję
observeEmails()
. Jeśli podczas wczytywania listy adresów e-mail w poprzednim stanie interfejsu nie wybrano wybranego adresu e-mail, ustaw go na pierwszy element:
ReplyHomeViewModel.kt
private fun observeEmails() {
viewModelScope.launch {
emailsRepository.getAllEmails()
.catch { ex ->
_uiState.value = ReplyHomeUIState(error = ex.message)
}
.collect { emails ->
val currentSelection = _uiState.value.selectedEmail
_uiState.value = ReplyHomeUIState(
emails = emails,
selectedEmail = currentSelection ?: emails.first()
)
}
}
}
- Wróć do
ReplyApp.kt
i użyj wybranego adresu e-mail, jeśli jest dostępny, aby wypełnić treść panelu szczegółów:
ReplyApp.kt
ListDetailPaneScaffold(
// ...
detailPane = {
if (replyHomeUIState.selectedEmail != null) {
ReplyDetailPane(replyHomeUIState.selectedEmail)
}
}
)
Uruchom aplikację jeszcze raz i zmień emulator na rozmiar tabletu. Zobaczysz, że kliknięcie elementu na liście powoduje zaktualizowanie zawartości panelu szczegółów.
Świetnie się to sprawdza, gdy widoczne są oba panele, ale gdy okno ma miejsce tylko na jeden, po kliknięciu elementu nic się nie dzieje. Przełącz widok emulatora na telefon lub urządzenie składane pionowo. Po kliknięciu elementu widoczny będzie tylko panel listy. Dzieje się tak, ponieważ wybrany adres e-mail zostanie zaktualizowany, ale ListDetailPaneScaffold
będzie nadal skupiać się na panelu listy w tych konfiguracjach.
- Aby rozwiązać ten problem, wstaw ten kod jako parametr lambda przekazywana do funkcji
ReplyListPane
:
ReplyApp.kt
ListDetailPaneScaffold(
// ...
listPane = {
ReplyListPane(
replyHomeUIState = replyHomeUIState,
onEmailClick = { email ->
onEmailClick(email)
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
}
)
},
// ...
)
Ta funkcja lambda używa utworzonego wcześniej nawigatora, aby dodać dodatkowe zachowanie po kliknięciu elementu. Wywołuje ona pierwotną funkcję lambda przekazaną do tej funkcji, a następnie wywołuje funkcję navigator.navigateTo()
, określającą, który panel ma być wyświetlany. Z każdym panelem rusztowania powiązana jest rola, a w panelu szczegółów to ListDetailPaneScaffoldRole.Detail
. W mniejszych oknach aplikacja będzie wyglądała, jakby przeszła do przodu.
Aplikacja musi również obsługiwać to, co się dzieje, gdy użytkownik naciśnie przycisk Wstecz w panelu szczegółów. To działanie będzie się różnić w zależności od tego, czy są widoczne jeden panel czy dwa.
- Obsługuj nawigację wstecz, dodając ten kod.
ReplyApp.kt
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
replyHomeUIState: ReplyHomeUIState,
onEmailClick: (Email) -> Unit,
) {
val navigator = rememberListDetailPaneScaffoldNavigator<Long>()
BackHandler(navigator.canNavigateBack()) {
navigator.navigateBack()
}
ListDetailPaneScaffold(
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
listPane = {
AnimatedPane {
ReplyListPane(
replyHomeUIState = replyHomeUIState,
onEmailClick = { email ->
onEmailClick(email)
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
}
)
}
},
detailPane = {
AnimatedPane {
if (replyHomeUIState.selectedEmail != null) {
ReplyDetailPane(replyHomeUIState.selectedEmail)
}
}
}
)
}
Nawigator zna pełny stan elementu ListDetailPaneScaffold
, czy możliwe jest przejście wstecz i co zrobić w każdym z tych scenariuszy. Ten kod tworzy BackHandler
, który jest włączony za każdym razem, gdy nawigator może przejść wstecz, a wewnątrz funkcji lambda wywołuje metodę navigateBack()
. Aby przejście między panelami było płynniejsze, każdy z nich jest zapakowany w kompozycję AnimatedPane()
.
Uruchom aplikację ponownie na emulatorze z możliwością zmiany rozmiaru na wszystkich typach urządzeń i zauważ, że po każdej zmianie konfiguracji ekranu lub w rozkładaniu urządzenia składanego zawartość ekranu i nawigacja zmieniają się dynamicznie w odpowiedzi na stan urządzenia. Możesz też kliknąć adresy e-mail w panelu listy i zobaczyć, jak układ zachowuje się na różnych ekranach, wyświetlając oba panele obok siebie lub płynnie animując je między nimi.
Gratulujemy. Udało Ci się dostosować swoją aplikację do różnych stanów i rozmiarów urządzeń. Możesz wypróbować aplikację na urządzeniach składanych, tabletach lub innych urządzeniach mobilnych.
6. Gratulacje
Gratulacje! Udało Ci się ukończyć to szkolenie i dowiedzieć się, jak dostosować aplikacje do potrzeb użytkowników dzięki Jetpack Compose.
Wiesz już, jak sprawdzić rozmiar i stan po złożeniu urządzenia oraz odpowiednio zaktualizować UI, nawigację i inne funkcje aplikacji. Wiesz już również, jak elastyczność poprawia osiągalność i poprawia wrażenia użytkowników.
Co dalej?
Zapoznaj się z pozostałymi ćwiczeniami z programowania dotyczącymi ścieżki tworzenia wiadomości.
Przykładowe aplikacje
- Przykładowe tworzenie wiadomości to zbiór wielu aplikacji, w których wykorzystano sprawdzone metody opisane w ćwiczeniach z programowania.