Twórz adaptacyjne aplikacje za pomocą Jetpack Compose

1. Wprowadzenie

Z tego Codelab dowiesz się, jak tworzyć elastyczne aplikacje na telefony, tablety i urządzenia składane oraz jak zwiększają one zasięg dzięki Jetpack Compose. Dowiesz się też, jak stosować komponenty i motywy Material 3.

Zanim przejdziemy do szczegółów, musimy zrozumieć, co rozumiemy przez adaptację.

Zdolność do przystosowania się

Interfejs aplikacji powinien być elastyczny, aby uwzględniać różne rozmiary, orientacje i formaty okien. Układ adaptacyjny zmienia się w zależności od dostępnej przestrzeni na ekranie. Te zmiany mogą obejmować proste dostosowanie układu w celu wypełnienia przestrzeni, wybór odpowiednich stylów nawigacji czy całkowitą zmianę układu w celu wykorzystania dodatkowej przestrzeni.

Aby dowiedzieć się więcej, zapoznaj się z artykułem Projektowanie adaptacyjne.

W tym ćwiczeniu poznasz sposób korzystania z adaptacyjności i jego zastosowanie w Jetpack Compose. Tworzysz aplikację o nazwie Reply, która pokazuje, jak zastosować adaptację do różnych ekranów oraz jak adaptacja i dostępność współdziałają, 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 korzystać z różnych typów nawigacji, aby ułatwić dotarcie do treści i ułatwić ich znalezienie.
  • Jak używać komponentów Material 3, aby zapewnić najlepszą jakość w każdym rozmiarze okna.

Czego potrzebujesz

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.

Emulator z możliwością zmiany rozmiaru z opcjami telefonu, tabletu i komputera.

Jeśli nie znasz Compose, przed rozpoczęciem pracy nad tym Codelab warto zapoznać się z podstawami Jetpack Compose.

Co utworzysz

  • Interaktywna aplikacja pocztowa o nazwie Reply, która wykorzystuje sprawdzone metody dotyczące elastycznych projektów, różne nawigacje w ramach interfejsu Material Design oraz optymalne wykorzystanie przestrzeni ekranu.

Prezentacja obsługi wielu urządzeń, którą stworzysz w ramach tego Codelab

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:

Zalecamy rozpoczęcie od kodu w gałęzi głównej i wykonywanie kolejnych kroków w ramach laboratorium kodu we własnym tempie.

Otwórz projekt w Android Studio

  1. W oknie Witamy w Android Studio kliknij c01826594f360d94.pngOtwórz istniejący projekt.
  2. Wybierz folder <Download Location>/AdaptiveUiCodelab (upewnij się, że wybrano katalog AdaptiveUiCodelab zawierający plik build.gradle).
  3. Gdy Android Studio zaimportuje projekt, sprawdź, czy możesz uruchomić gałąź main.

Poznaj kod startowy

Kod gałęzi głównej zawiera pakiet ui. W tym pakiecie będziesz pracować z tymi plikami:

  • MainActivity.kt – aktywność punktu wejścia, w której uruchamiasz aplikację.
  • ReplyApp.kt – zawiera komponenty interfejsu na ekranie głównym.
  • ReplyHomeViewModel.kt – zawiera dane i stan interfejsu użytkownika dotyczący treś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 o zmiennym rozmiarze i wypróbujesz ją na różnych typach urządzeń, np. na telefonie lub tablecie, interfejs będzie po prostu rozszerzał się do danej przestrzeni zamiast wykorzystywać całą dostępną przestrzeń ekranu lub zapewniać ergonomię dotykowa.

Ekran początkowy na telefonie

Początkowy rozciągnięty widok na tablecie

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 omawiamy, co oznacza dostosowywanie aplikacji i jakie komponenty zapewnia Material 3, aby to ułatwić. Dotyczy ona też typów ekranów i stanów, na które chcesz kierować reklamy, w tym telefonów, tabletów, dużych tabletów i urządzeń składanych.

Na początek poznasz podstawy dotyczące 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 była bardziej elastyczna.

Rozmiary okien

Urządzenia z Androidem występują w różnych kształtach i rozmiarach, od telefonów po tablety i urządzenia z ChromeOS. Aby obsługiwać jak najwięcej rozmiarów okien, interfejs użytkownika musi być elastyczny i dopasowywać się do ekranu. Aby pomóc Ci znaleźć odpowiedni próg, po przekroczeniu którego należy zmienić interfejs aplikacji, zdefiniowaliśmy wartości punktów przecięcia, które pomagają klasyfikować urządzenia do wstępnie zdefiniowanych klas rozmiarów (kompaktowy, średni i rozwinięty), zwanych klasami rozmiarów okna. To zestaw punktów przełamania widoku, które pomagają projektować, tworzyć i testować elastyczne i adaptacyjne układy aplikacji.

Kategorie zostały wybrane tak, aby zapewnić równowagę między prostotą układu a elastycznością w optymalizowaniu aplikacji pod kątem wyjątkowych przypadków. Klasa rozmiaru okna jest zawsze określana przez przestrzeń ekranu dostępną dla aplikacji, która może nie obejmować całego fizycznego ekranu w przypadku wielozadaniowości lub innych podziałów.

WindowWidthSizeClass dla kompaktowej, średniej i rozszerzonej szerokości.

WindowHeightSizeClass dla kompaktowej, średniej i rozwiniętej wysokości.

Szerokość i wysokość są klasyfikowane osobno, więc w dowolnym momencie aplikacja ma 2 klasy rozmiarów 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, dlatego w tym przypadku należy też używać klas rozmiarów szerokości.

Stany zwinięcia

Złożone urządzenia dają jeszcze więcej możliwości dostosowania aplikacji ze względu na ich różne rozmiary i obecność zawiasów. Zawiasy mogą zasłaniać część wyświetlacza, przez co nie będzie można w nim wyświetlać treści. Mogą się też rozdzielić, co oznacza, że po rozłożeniu urządzenia dostępne są 2 oddzielne fizyczne wyświetlacze.

składanie, płaskie i półotwarte;

Użytkownik może też patrzeć na wyświetlacz wewnętrzny, gdy zawias jest częściowo otwarty, co powoduje różne pozycje fizyczne w zależności od orientacji składania: na stole (składanie poziome, pokazane po prawej stronie na powyższym obrazku) i jak książka (składanie pionowe).

Dowiedz się więcej o ustawieniach i zawiasach po złożeniu.

Wszystkie te kwestie należy wziąć pod uwagę podczas implementowania układów dostosowanych do urządzeń składanych.

Uzyskaj informacje adaptacyjne

Biblioteka Material 3 adaptive zapewnia wygodny dostęp do informacji o oknie, w którym działa aplikacja.

  1. 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" }
  1. W pliku build modułu aplikacji dodaj nową zależność biblioteki, a potem zsynchronizuj 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 sekcji MainActivity.

  1. W onCreate() w bloku ReplyTheme pobierz informacje o dostosowaniu do okna i wyświetl klasy rozmiarów w komponencie Text. Możesz dodać to po elemencie ReplyApp():

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 zobaczysz klasy rozmiarów okien nałożone na zawartość aplikacji. Zapoznaj się z informacjami w oknie. Następnie możesz je usunąć Text, ponieważ obejmują one zawartość aplikacji i nie będą potrzebne do wykonania kolejnych kroków.

4. Dynamiczna nawigacja

Teraz dostosujesz nawigację w aplikacji do stanu i rozmiaru urządzenia, aby ułatwić korzystanie z aplikacji.

Gdy użytkownicy trzymają telefon, ich palce zwykle znajdują się u dołu ekranu. Gdy użytkownicy trzymają otwarte urządzenie składane lub tablet, ich palce zwykle znajdują się blisko boków. Użytkownicy powinni mieć możliwość nawigowania po aplikacji lub inicjowania interakcji z aplikacją bez konieczności przyjmowania niewygodnych pozycji rąk lub zmiany ich położenia.

Podczas projektowania aplikacji i decydowania, gdzie w układzie umieścić interaktywne elementy interfejsu, weź pod uwagę wpływ ergonomii na różne obszary ekranu.

  • Wskaż obszary, w których można wygodnie sięgnąć, trzymając urządzenie.
  • Do których obszarów można dotrzeć tylko po rozciągnięciu palców, co może być niewygodne?
  • Do których obszarów trudno jest dotrzeć lub które znajdują się daleko od miejsca, w którym użytkownik trzyma urządzenie?

Nawigacja jest pierwszą rzeczą, z którą użytkownicy wchodzą w interakcję, i zawiera ważne działania związane z kluczowymi ścieżkami użytkowników, dlatego powinna być umieszczona w miejscach, do których łatwo jest 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

Nawigacja u dołu ekranu doskonale sprawdza się w przypadku małych urządzeń, ponieważ trzymamy je w naturalnej pozycji, w której kciuk może łatwo dosięgnąć wszystkich punktów dotykowych nawigacji u dołu ekranu. Używaj go, gdy masz kompaktowe urządzenie lub składany telefon w kompaktnym stanie.

Dolna belka nawigacyjna z elementami

W przypadku średniej szerokości okna pasek nawigacyjny jest idealny pod względem dostępności, ponieważ kciuk naturalnie opada na bok urządzenia. Aby wyświetlić więcej informacji, możesz też połączyć pasek nawigacyjny z drawerem nawigacji.

Kolumna nawigacji z elementami

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 nawigacyjnych: modalna i stała.

Panel nawigacji modalnej

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. Czasami można go połączyć z listwą nawigacyjną.

Panel nawigacji w trybie dialogowym z elementami

Stały panel nawigacji

Na dużych tabletach, Chromebookach i komputerach możesz używać stałego paska nawigacyjnego.

Panel nawigacji z produktami

Wdrażanie nawigacji dynamicznej

Teraz przełączaj 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 Material NavigationSuiteScaffold, aby automatycznie przełączać się między różnymi komponentami nawigacji na podstawie informacji takich jak bieżąca klasa rozmiaru okna.

  1. Aby uzyskać ten komponent, dodaj zależność Gradle, aktualizując katalog wersji i skrypt kompilacji aplikacji, a następnie zsynchronizuj 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)
}
  1. Znajdź funkcję kompozytową ReplyNavigationWrapper()ReplyApp.kt i zastąp Column oraz jego zawartość przez 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. Wewnątrz ostatniej funkcji lambda ten kod wywołuje funkcję 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 jednak wprowadzić niewielką zmianę.

  1. Dodaj ten kod tuż przed wywołaniem funkcji 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ą funkcji currentWindowSize()LocalDensity.current, a potem porównuje szerokość okna, aby określić typ układu interfejsu nawigacji. Jeśli szerokość okna wynosi co najmniej 1200.dp, jest używany element NavigationSuiteType.NavigationDrawer. W przeciwnym razie zostanie użyte domyślne obliczenie.

Gdy uruchomisz aplikację ponownie na emulowanym urządzeniu z możliwością zmiany rozmiaru i wypróbujesz różnych typów, zwróć uwagę, że gdy tylko zmieni się konfiguracja ekranu lub rozłożysz składane urządzenie, nawigacja zmieni się na odpowiednią dla danego rozmiaru.

Zmiany w dopasowaniu do różnych rozmiarów urządzeń.

Gratulacje, znasz już różne sposoby nawigacji dla różnych rozmiarów i stanów okien.

W następnej sekcji dowiesz się, 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

Niezależnie od tego, czy uruchamiasz aplikację na małym tablecie, rozłożonym urządzeniu czy dużym tablecie, ekran jest rozciągany, aby wypełnić pozostałą przestrzeń. 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 kanoniczne układy, z których każdy ma konfiguracje dla kompaktowych, średnich i rozwiniętych rozmiarów okna. W tym przypadku idealnie nadaje się układ kanoniczny Szczegóły listy. Jest on dostępny podczas tworzenia wiadomości jako ListDetailPaneScaffold.

  1. Aby uzyskać ten komponent, dodaj te zależności i wykonaj 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)
}
  1. Znajdź w ReplyApp.kt funkcję typu composable ReplyAppContent(), która obecnie wyświetla tylko panel listy po wywołaniu funkcji ReplyListPane(). Zastąp tę implementację implementacją ListDetailPaneScaffold, wstawiając ten kod. Ponieważ jest to eksperymentalne API, musisz też dodać adnotację @OptIn do funkcji ReplyAppContent():

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ą funkcji rememberListDetailPaneNavigator(). Nawigator umożliwia kontrolowanie, które panele są wyświetlane i jakie treści powinny się w nich znajdować. Pokażę to później.

ListDetailPaneScaffold pokaże 2 panele, gdy klasa rozmiaru szerokości okna zostanie rozwinięta. W przeciwnym razie będzie ono wyświetlać jedną lub drugą stronę na podstawie wartości podanych dla 2 parametrów: dyrektywy szablonu i wartości szablonu. 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. Dane ReplyListPane()ReplyDetailPane() (znajdujące się w ReplyListContent.kt) służą do wypełniania odpowiednio ról panelu listy i panelu szczegółów. Funkcja ReplyDetailPane() oczekuje argumentu e-mail, więc na razie ten kod używa pierwszego adresu e-mail z listy adresów w 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. Wygląda to już znacznie lepiej niż wcześniej.

Teraz przyjrzyjmy się oczekiwanemu działaniu tego ekranu. 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 e-mail został wybrany, a kliknięcie elementu nie powoduje żadnej reakcji. Najlepszym miejscem na przechowywanie tych informacji jest ReplyHomeUIState.

  1. Otwórz ReplyHomeViewModel.kt i znajdź klasę danych ReplyHomeUIState. Dodaj właściwość dla wybranego e-maila 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
)
  1. W tym samym pliku funkcja ReplyHomeViewModel ma funkcję setSelectedEmail(), która jest wywoływana, gdy użytkownik kliknie element listy. Zmodyfikuj tę funkcję, aby skopiować stan interfejsu użytkownika i zapisać 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? W takim przypadku możesz stosować różne rozwiązania, np. wyświetlać domyślnie pierwszy element na liście.

  1. W tym samym pliku zmodyfikuj funkcję observeEmails(). Jeśli po załadowaniu listy e-maili poprzedni stan interfejsu nie zawiera wybranego e-maila, ustaw go jako pierwszy:

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()
                )
            }
    }
}
  1. 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 ponownie aplikację i przełącz emulator na rozmiar tabletu. Sprawdź, czy kliknięcie elementu na liście powoduje zaktualizowanie zawartości panelu szczegółów.

To działa świetnie, gdy obie karty są widoczne, ale gdy w oknie jest miejsce tylko na jedną kartę, wydaje się, że po kliknięciu elementu nic się nie dzieje. Spróbuj przełączyć widok emulatora na telefon lub składane urządzenie w orientacji pionowej i zwróć uwagę, że nawet po kliknięciu elementu widoczna jest tylko panel listy. Dzieje się tak, ponieważ mimo że wybrany e-mail jest aktualizowany, w tych konfiguracjach ListDetailPaneScaffold pozostaje skupiony na panelu listy.

  1. Aby to naprawić, wstaw ten kod jako funkcję lambda przekazywaną do funkcji ReplyListPane:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

Ta funkcja lambda korzysta z utworzonego wcześniej navigatora, aby dodać dodatkowe działanie po kliknięciu elementu. Wywołuje ona oryginalną funkcję lambda przekazaną do tej funkcji, a następnie wywołuje funkcję navigator.navigateTo(), która określa, 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 będzie to wyglądać tak, jakby aplikacja przewinęła się do przodu.

Aplikacja musi też określić, co się stanie, gdy użytkownik naciśnie przycisk Wstecz w panelu szczegółów. Zachowanie to będzie się różnić w zależności od tego, czy widoczny jest 1 czy 2 panele.

  1. Dodaj poniższy kod, aby umożliwić cofanie się.

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 cofnięcie się do poprzedniego stanu, i wie, co robić w każdym z tych scenariuszy. Ten kod tworzy BackHandler, który jest włączony, gdykolwiek nawigator może wrócić, i w wezwaniach lambda navigateBack(). Aby przejścia między panelami były płynniejsze, każdy z nich jest ujęty w komponent 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. Spróbuj też kliknąć e-maile w panelu listy i sprawdzić, jak układ zachowuje się na różnych ekranach, wyświetlając oba panele obok siebie lub płynnie przechodząc między nimi.

Wyświetlam zmiany możliwości adaptacyjnych w przypadku urządzeń o różnej wielkości.

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.

Dowiedz się, jak sprawdzić rozmiar i stan złożenia urządzenia oraz zaktualizować interfejs użytkownika, nawigację i inne funkcje aplikacji. Wiesz już również, jak elastyczność poprawia osiągalność i poprawia wrażenia użytkowników.

Co dalej?

Sprawdź inne laboratoria kodu na ścieżce Compose.

Przykładowe aplikacje

  • Przykłady kodu to zbiór wielu aplikacji, które wykorzystują sprawdzone metody opisane w laboratoriach programowania.

Dokumenty referencyjne