1. Wprowadzenie
Z tego modułu dowiesz się, jak tworzyć aplikacje adaptacyjne na telefony, tablety i urządzenia składane oraz jak zwiększać ich dostępność za pomocą Jetpack Compose. Poznasz też sprawdzone metody korzystania z komponentów Material 3 i motywów.
Zanim zaczniemy, warto wyjaśnić, co rozumiemy przez adaptacyjność.
Możliwość dostosowania
Interfejs aplikacji powinien być elastyczny, aby uwzględniać różne rozmiary okien, orientacje i formy. Układ adaptacyjny zmienia się w zależności od dostępnego miejsca na ekranie. Zmiany te obejmują proste dostosowania układu, aby wypełnić przestrzeń, wybór odpowiednich stylów nawigacji i całkowitą zmianę układu w celu wykorzystania dodatkowej przestrzeni.
Więcej informacji znajdziesz w artykule Projektowanie adaptacyjne.
W tym laboratorium dowiesz się, jak korzystać z adaptacyjności w Jetpack Compose i jak o niej myśleć. Tworzysz aplikację o nazwie Reply, która pokazuje, jak wdrożyć adaptacyjność na wszystkich rodzajach ekranów oraz jak adaptacyjność i dostępność współpracują ze sobą, aby zapewnić użytkownikom optymalne wrażenia.
Czego się nauczysz
- Jak zaprojektować aplikację, aby była dostosowana do wszystkich rozmiarów okien za pomocą Jetpack Compose.
- Jak kierować aplikację na różne urządzenia składane.
- Jak używać różnych typów nawigacji, aby zwiększyć dostępność i ułatwić korzystanie z witryny.
- Jak używać komponentów Material 3, aby zapewnić najlepsze wrażenia przy każdym rozmiarze okna.
Czego potrzebujesz
- Najnowsza stabilna wersja Androida Studio.
- Urządzenie wirtualne z Androidem 13, którego rozmiar można zmieniać.
- znajomość języka Kotlin,
- Podstawowa znajomość Compose (np. adnotacji
@Composable). - Podstawowa znajomość układów Compose (np.
RowiColumn). - Podstawowa znajomość modyfikatorów (np.
Modifier.padding()).
W tym ćwiczeniu z programowania użyjesz emulatora z możliwością zmiany rozmiaru, który umożliwia przełączanie się między różnymi typami urządzeń i rozmiarami okien.

Jeśli nie znasz Compose, przed ukończeniem tego laboratorium kodu zapoznaj się z podstawami Jetpack Compose.
Co utworzysz
- Interaktywna aplikacja klienta poczty e-mail o nazwie Reply, która wykorzystuje sprawdzone metody projektowania adaptacyjnego, różne rodzaje nawigacji Material i optymalne wykorzystanie przestrzeni ekranu.

2. Konfiguracja
Aby pobrać kod do tego ćwiczenia, sklonuj repozytorium GitHub z 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:
Zalecamy rozpoczęcie od kodu w gałęzi main i postępować zgodnie z instrukcjami w tym samouczku w własnym tempie.
Otwieranie projektu w Android Studio
- W oknie Witamy w Android Studio kliknij
Otwórz istniejący projekt. - Wybierz folder
<Download Location>/AdaptiveUiCodelab(upewnij się, że wybierasz katalogAdaptiveUiCodelabzawierającybuild.gradle). - Gdy Android Studio zaimportuje projekt, sprawdź, czy możesz uruchomić gałąź
main.
Poznaj kod startowy
Kod oddziału main zawiera pakiet ui. W tym pakiecie będziesz pracować z tymi plikami:
MainActivity.kt– aktywność w punkcie wejścia, w którym uruchamiasz aplikację.ReplyApp.kt– zawiera komponenty kompozycyjne interfejsu ekranu głównego.ReplyHomeViewModel.kt– udostępnia dane i stan interfejsu treści aplikacji.ReplyListContent.kt– zawiera funkcje kompozycyjne do wyświetlania list i ekranów szczegółów.
Jeśli uruchomisz tę aplikację na emulatorze o zmienianym rozmiarze i wypróbujesz różne typy urządzeń, np. telefon lub tablet, interfejs użytkownika po prostu rozszerzy się do dostępnej przestrzeni, zamiast wykorzystać miejsce na ekranie lub zapewnić ergonomię w zakresie dostępności.


Zaktualizujesz go, aby wykorzystać przestrzeń ekranu, zwiększyć użyteczność i poprawić ogólne wrażenia użytkowników.
3. Dostosowywanie aplikacji
W tej sekcji dowiesz się, co oznacza dostosowywanie aplikacji i jakie komponenty Material 3 ułatwiają to zadanie. Obejmuje też typy ekranów i stany, na które będziesz kierować reklamy, w tym telefony, tablety, duże tablety i urządzenia składane.
Na początek poznasz podstawy rozmiarów okien, pozycji składania i różnych typów opcji nawigacji. Następnie możesz użyć tych interfejsów API w aplikacji, aby zwiększyć jej możliwości adaptacyjne.
Rozmiary okien
Urządzenia z Androidem mają różne kształty i rozmiary – od telefonów po urządzenia składane, tablety i urządzenia z ChromeOS. Aby obsługiwać jak najwięcej rozmiarów okien, interfejs musi być elastyczny i dostosowywać się do różnych warunków. Aby pomóc Ci znaleźć odpowiedni próg, przy którym należy zmienić interfejs aplikacji, zdefiniowaliśmy wartości punktów przerwania, które pomagają klasyfikować urządzenia w predefiniowanych klasach rozmiarów (kompaktowe, średnie i rozszerzone), zwanych klasami rozmiarów okna. Jest to zestaw opartych na opiniach punktów przerwania widoku, które pomagają projektować, tworzyć i testować elastyczne i adaptacyjne układy aplikacji.
Kategorie zostały wybrane tak, aby zachować prostotę układu i umożliwić optymalizację aplikacji w unikalnych przypadkach. Klasa rozmiaru okna jest zawsze określana przez przestrzeń ekranu dostępną dla aplikacji, która w przypadku wielozadaniowości lub innych podziałów może nie obejmować całego ekranu fizycznego.


Szerokość i wysokość są klasyfikowane oddzielnie, więc w każdym momencie aplikacja ma 2 klasy rozmiaru okna – jedną dla szerokości i jedną dla wysokości. Dostępna szerokość jest zwykle ważniejsza niż dostępna wysokość ze względu na powszechność przewijania w pionie, więc w tym przypadku użyjesz też klas rozmiaru szerokości.
Stany zwinięcia
Urządzenia składane stwarzają jeszcze więcej sytuacji, do których Twoja aplikacja może się dostosować, ze względu na ich różne rozmiary i obecność zawiasów. Zawiasy mogą zasłaniać część wyświetlacza, przez co ten obszar nie nadaje się do wyświetlania treści. Mogą też rozdzielać wyświetlacz, co oznacza, że po rozłożeniu urządzenia są 2 osobne wyświetlacze.

Użytkownik może też patrzeć na wewnętrzny ekran przy częściowo otwartym zawiasie, co powoduje różne pozycje fizyczne w zależności od orientacji zgięcia: pozycja stołowa (zgięcie poziome, widoczne po prawej stronie na powyższym obrazie) i pozycja książkowa (zgięcie pionowe).
Dowiedz się więcej o postawach składania i zawiasach.
Wszystkie te kwestie należy wziąć pod uwagę podczas wdrażania układów adaptacyjnych obsługujących urządzenia składane.
Uzyskiwanie informacji adaptacyjnych
Biblioteka Material3 adaptive zapewnia wygodny dostęp do informacji o oknie, w którym działa aplikacja.
- Dodaj do pliku katalogu wersji wpisy dotyczące tego artefaktu i jego wersji:
gradle/libs.versions.toml
[versions]
material3Adaptive = "1.0.0"
[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 potem przeprowadź synchronizację Gradle:
app/build.gradle.kts
dependencies {
implementation(libs.androidx.material3.adaptive)
}
Teraz w dowolnym zakresie kompozycyjnym możesz użyć funkcji currentWindowAdaptiveInfo(), aby uzyskać obiekt WindowAdaptiveInfo zawierający informacje takie jak bieżąca klasa rozmiaru okna i to, czy urządzenie jest w pozycji składanej, np. w pozycji stołowej.
Możesz to teraz wypróbować w MainActivity.
- W bloku
onCreate()wewnątrz blokuReplyThemepobierz informacje o dostosowywaniu okna i wyświetl klasy rozmiaru w funkcji kompozycyjnejText. Możesz dodać go 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 nad jej treścią będą widoczne klasy rozmiaru okna. Zapoznaj się z informacjami o oknie adaptacyjnym. Następnie możesz usunąć ten element Text, ponieważ zasłania on zawartość aplikacji i nie będzie potrzebny w dalszych krokach.
4. Dynamiczna nawigacja
Teraz dostosujesz nawigację aplikacji do zmian stanu i rozmiaru urządzenia, aby ułatwić korzystanie z niej.
Gdy użytkownicy trzymają telefon, zwykle palce znajdują się u dołu ekranu. Gdy użytkownicy trzymają otwarte urządzenie składane lub tablet, ich palce zwykle znajdują się blisko krawędzi. Użytkownicy powinni mieć możliwość poruszania się po aplikacji i wchodzenia z nią w interakcje bez konieczności przyjmowania niewygodnych pozycji dłoni lub zmiany ich ułożenia.
Podczas projektowania aplikacji i decydując, gdzie umieścić interaktywne elementy interfejsu w układzie, weź pod uwagę ergonomiczne aspekty różnych obszarów ekranu.
- Które obszary są łatwo dostępne podczas trzymania urządzenia?
- Do których obszarów można dotrzeć tylko przez wyciągnięcie palców, co może być niewygodne?
- Które obszary są trudne do osiągnięcia lub znajdują się daleko od miejsca, w którym użytkownik trzyma urządzenie?
Nawigacja to pierwsza rzecz, z którą użytkownicy wchodzą w interakcję. Zawiera ona ważne działania związane z kluczowymi ścieżkami użytkowników, dlatego powinna być umieszczona w miejscach, do których najłatwiej dotrzeć. Biblioteka adaptacyjna Material zawiera kilka komponentów, które pomagają w implementacji nawigacji w zależności od klasy rozmiaru okna urządzenia.
Dolna nawigacja
Nawigacja u dołu ekranu idealnie sprawdza się w przypadku kompaktowych urządzeń, ponieważ naturalnie trzymamy je w taki sposób, aby kciukiem łatwo było dotrzeć do wszystkich punktów dotykowych nawigacji u dołu ekranu. Używaj go, gdy masz małe urządzenie lub składany telefon w kompaktowej formie.

Kolumna nawigacji
W przypadku okna o średniej szerokości pasek nawigacyjny jest idealny pod względem dostępności, ponieważ kciuk naturalnie układa się wzdłuż boku urządzenia. Możesz też połączyć pasek nawigacyjny z panelem nawigacyjnym, aby wyświetlać więcej informacji.

Panel nawigacji
Panel nawigacji ułatwia wyświetlanie szczegółowych informacji na kartach nawigacji i jest łatwo dostępny podczas korzystania z tabletów lub większych urządzeń. Dostępne są 2 rodzaje paneli nawigacyjnych: modalny i stały.
Modalny panel nawigacji
W przypadku telefonów i tabletów o kompaktowych i średnich rozmiarach możesz używać modalnego panelu nawigacji, który można rozwinąć lub ukryć jako nakładkę na treść. Czasami można go połączyć z panelem nawigacyjnym.

Stały panel nawigacji
Możesz używać stałego panelu nawigacyjnego do stałej nawigacji na dużych tabletach, Chromebookach i komputerach stacjonarnych.

Wdrażanie dynamicznej nawigacji
Teraz będziesz przełączać się między różnymi typami nawigacji w zależności od stanu i rozmiaru urządzenia.
Obecnie aplikacja zawsze wyświetla NavigationBar pod treścią ekranu, niezależnie od stanu urządzenia. Zamiast tego możesz użyć komponentu Material NavigationSuiteScaffold, aby automatycznie przełączać się między różnymi komponentami nawigacji na podstawie informacji takich jak bieżąca klasa rozmiaru okna.
- Aby uzyskać ten komponent, dodaj zależność Gradle, aktualizując katalog wersji i skrypt kompilacji aplikacji, a następnie przeprowadź synchronizację Gradle:
gradle/libs.versions.toml
[versions]
material3AdaptiveNavSuite = "1.3.0"
[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ę typu „composable”
ReplyNavigationWrapper()w plikuReplyApp.kti zastąpColumnoraz jego zawartość elementemNavigationSuiteScaffold:
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 w przypadku dodawania elementów w LazyColumn. W funkcji lambda na końcu ten kod wywołuje funkcję content() przekazaną jako argument do funkcji ReplyNavigationWrapperUI().
Uruchom aplikację na emulatorze i spróbuj zmieniać rozmiary między telefonem, urządzeniem składanym i tabletem. Zobaczysz, jak pasek nawigacyjny zmienia się w panel nawigacyjny i z powrotem.
W przypadku bardzo szerokich okien, np. na tablecie w trybie poziomym, możesz wyświetlać stały panel nawigacyjny. NavigationSuiteScaffold obsługuje wyświetlanie stałej szuflady, ale nie jest ona widoczna w żadnej z obecnych wartości WindowWidthSizeClass. Możesz jednak to zmienić, wprowadzając niewielką modyfikację.
- Dodaj ten kod tuż przed wywołaniem
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 przekształca go na jednostki DP za pomocą funkcji currentWindowSize() i LocalDensity.current, a następnie porównuje szerokość okna, aby określić typ układu interfejsu nawigacji. Jeśli szerokość okna wynosi co najmniej 1200.dp, używana jest wartość NavigationSuiteType.NavigationDrawer. W przeciwnym razie użyje domyślnego obliczenia.
Gdy ponownie uruchomisz aplikację na emulatorze o zmienianym rozmiarze i wypróbujesz różne typy, zauważysz, że za każdym razem, gdy zmienia się konfiguracja ekranu lub rozkładasz urządzenie składane, nawigacja zmienia się na odpowiedni typ dla danego rozmiaru.

Gratulacje! Poznałeś(-aś) różne typy nawigacji, które obsługują różne rozmiary i stany okien.
W następnej sekcji dowiesz się, jak wykorzystać pozostałą część ekranu zamiast rozciągać ten sam element listy na całą szerokość.
5. Wykorzystanie miejsca na ekranie
Niezależnie od tego, czy używasz aplikacji na małym tablecie, otwartym urządzeniu czy dużym tablecie, ekran jest rozciągany, aby wypełnić pozostałą przestrzeń. Chcesz mieć pewność, że możesz wykorzystać tę przestrzeń ekranu, aby wyświetlać więcej informacji, np. w tej aplikacji pokazywać użytkownikom e-maile i wątki na tej samej stronie.
Material 3 definiuje 3 kanoniczne układy, z których każdy ma konfiguracje dla klas rozmiaru okna: kompaktowej, średniej i rozszerzonej. Kanoniczny układ Szczegóły listy idealnie nadaje się do tego zastosowania i jest dostępny w komponowaniu jako ListDetailPaneScaffold.
- Aby uzyskać 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ę typu „composable”
ReplyAppContent()w plikuReplyApp.kt, która obecnie wyświetla tylko panel listy, wywołując funkcjęReplyListPane(). Zastąp tę implementację kodemListDetailPaneScaffold, wstawiając ten kod: Ponieważ jest to eksperymentalny interfejs API, dodasz też adnotację@OptIndo 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 nawigatora za pomocą rememberListDetailPaneNavigator. Nawigator umożliwia pewną kontrolę nad tym, który panel jest wyświetlany i jakie treści powinny się w nim znajdować. Zostanie to zademonstrowane w dalszej części artykułu.
ListDetailPaneScaffold będzie wyświetlać 2 panele, gdy klasa rozmiaru szerokości okna będzie rozwinięta. W przeciwnym razie wyświetli jeden z tych paneli na podstawie wartości podanych w przypadku 2 parametrów: dyrektywy szkieletu i wartości szkieletu. Aby uzyskać domyślne zachowanie, ten kod używa dyrektywy scaffold i wartości scaffold podanej przez nawigatora.
Pozostałe wymagane parametry to lambdy kompozycyjne dla paneli. Dane ReplyListPane() i ReplyDetailPane() (znajdujące się w ReplyListContent.kt) służą do wypełniania ról odpowiednio w okienkach listy i szczegółów. ReplyDetailPane() oczekuje argumentu e-mail, więc na razie ten kod używa pierwszego adresu e-mail z listy adresów e-mail w ReplyHomeUIState.
Uruchom aplikację i przełącz widok emulatora na urządzenie składane lub tablet (może być też konieczna zmiana orientacji), aby zobaczyć układ dwupanelowy. Wygląda to już o wiele lepiej niż wcześniej.
Teraz omówmy niektóre z oczekiwanych zachowań na tym ekranie. Gdy użytkownik kliknie e-maila w panelu listy, powinien on zostać wyświetlony w panelu szczegółów wraz ze wszystkimi odpowiedziami. Obecnie aplikacja nie śledzi, który e-mail został wybrany, a kliknięcie elementu nie powoduje żadnej reakcji. Najlepszym miejscem do przechowywania tych informacji jest reszta stanu interfejsu w ReplyHomeUIState.
- Otwórz
ReplyHomeViewModel.kti znajdź klasę danychReplyHomeUIState. Dodaj właściwość do wybranego adresu e-mail z domyślną wartością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
ReplyHomeViewModelma funkcjęsetSelectedEmail(), która jest wywoływana, gdy użytkownik kliknie element listy. Zmodyfikuj tę funkcję, aby skopiować stan interfejsu i zapisać wybrany adres e-mail:
ReplyHomeViewModel.kt
fun setSelectedEmail(email: Email) {
_uiState.update {
it.copy(selectedEmail = email)
}
}
Warto zastanowić się, co się stanie, zanim użytkownik kliknie jakikolwiek element, a wybrany adres e-mail będzie null. Co powinno być wyświetlane w panelu szczegółów? Można to rozwiązać na kilka sposobów, np. domyślnie wyświetlać pierwszy element na liście.
- W tym samym pliku zmodyfikuj funkcję
observeEmails(). Gdy lista e-maili zostanie załadowana, a w poprzednim stanie interfejsu nie było wybranego e-maila, 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.kti użyj wybranego adresu e-mail, jeśli jest dostępny, aby wypełnić zawartość panelu szczegółów:
ReplyApp.kt
ListDetailPaneScaffold(
// ...
detailPane = {
if (replyHomeUIState.selectedEmail != null) {
ReplyDetailPane(replyHomeUIState.selectedEmail)
}
}
)
Ponownie uruchom aplikację i przełącz emulator na rozmiar tabletu. Zobaczysz, że kliknięcie elementu listy powoduje zaktualizowanie zawartości panelu szczegółów.
Działa to świetnie, gdy oba panele są widoczne, ale gdy okno ma miejsce tylko na jeden panel, po kliknięciu elementu wydaje się, że nic się nie dzieje. Spróbuj przełączyć widok emulatora na telefon lub urządzenie składane w orientacji pionowej. Zauważysz, że nawet po kliknięciu elementu widoczny jest tylko panel listy. Dzieje się tak, ponieważ mimo że wybrany adres e-mail jest aktualizowany, w tych konfiguracjach ListDetailPaneScaffold pozostaje skupiony na panelu listy.
- Aby to naprawić, wstaw ten kod jako funkcję lambda przekazaną do
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 działanie po kliknięciu elementu. Wywoła ona oryginalną funkcję lambda przekazaną do tej funkcji, a następnie wywoła navigator.navigateTo(), określając, który panel ma być wyświetlany. Każdy panel w szkielecie ma przypisaną rolę, a w przypadku panelu szczegółów jest to ListDetailPaneScaffoldRole.Detail. W mniejszych oknach będzie to wyglądać tak, jakby aplikacja przeszła do przodu.
Aplikacja musi też obsługiwać sytuację, w której użytkownik naciśnie przycisk Wstecz w panelu szczegółów. Działanie aplikacji będzie się różnić w zależności od tego, czy widoczny jest jeden panel, czy dwa.
- Dodaj ten kod, aby obsługiwać przechodzenie wstecz.
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 ListDetailPaneScaffold, wie, czy możliwe jest przechodzenie wstecz, i wie, co zrobić w każdym z tych scenariuszy. Ten kod tworzy BackHandler, który jest włączony, gdy nawigator może wrócić, a w funkcji lambda wywołuje navigateBack(). Aby przejście między panelami było płynniejsze, każdy z nich jest opakowany w funkcję kompozycyjną AnimatedPane().
Uruchom ponownie aplikację na emulatorze o zmiennej wielkości, aby sprawdzić, jak działa na różnych typach urządzeń. Zauważ, że gdy tylko zmieni się konfiguracja ekranu lub rozłożysz urządzenie składane, nawigacja i treści na ekranie dynamicznie dostosują się do zmiany stanu urządzenia. Spróbuj też klikać e-maile w panelu listy i sprawdź, jak układ zachowuje się na różnych ekranach, wyświetlając oba panele obok siebie lub płynnie przechodząc między nimi.

Gratulacje! Twoja aplikacja jest teraz dostosowana do wszystkich rodzajów stanów i rozmiarów urządzeń. Możesz wypróbować działanie aplikacji na urządzeniach składanych, tabletach i innych urządzeniach mobilnych.
6. Gratulacje
Gratulacje! Udało Ci się ukończyć to ćwiczenie z programowania i dowiedzieć się, jak tworzyć adaptacyjne aplikacje za pomocą Jetpack Compose.
Dowiedziałeś(-aś) się, jak sprawdzić rozmiar urządzenia i stan złożenia oraz odpowiednio zaktualizować interfejs, nawigację i inne funkcje aplikacji. Dowiedzieliśmy się też, jak elastyczność zwiększa zasięg i poprawia wygodę użytkowników.
Co dalej?
Zapoznaj się z innymi codelabami na ścieżce Compose.
Przykładowe aplikacje
- Przykłady Compose to zbiór wielu aplikacji, które wykorzystują sprawdzone metody opisane w Codelabs.