1. Wprowadzenie
Ekosystem urządzeń z Androidem stale się rozwija. Od wbudowanych klawiatur sprzętowych po nowoczesne urządzenia składane, tablety i okna o dowolnym rozmiarze – aplikacje na Androida nigdy nie działały na tak różnorodnych urządzeniach jak obecnie.
To świetna wiadomość dla deweloperów, ale aby spełnić oczekiwania użytkowników i zapewnić im doskonałe wrażenia na ekranach o różnych rozmiarach, konieczne jest zoptymalizowanie aplikacji. Zamiast kierować reklamy na każde nowe urządzenie z osobna, możesz użyć elastycznego interfejsu i odpornej architektury, aby Twoja aplikacja wyglądała i działała świetnie wszędzie tam, gdzie są Twoi obecni i przyszli użytkownicy – na urządzeniach o dowolnym rozmiarze i kształcie.
Wprowadzenie środowisk Androida z możliwością zmiany rozmiaru w dowolnym kierunku to świetny sposób na przetestowanie interfejsu użytkownika dostosowującego się do różnych urządzeń. W tym module dowiesz się, jakie konsekwencje ma zmiana rozmiaru, i poznasz sprawdzone metody, które pozwolą Ci łatwo i skutecznie dostosowywać rozmiar aplikacji.
Co utworzysz
Poznasz konsekwencje zmiany rozmiaru w dowolnym kierunku i zoptymalizujesz aplikację na Androida, aby zademonstrować sprawdzone metody zmiany rozmiaru. Twoja aplikacja będzie:
mieć zgodny plik manifestu,
- Usuwanie ograniczeń, które uniemożliwiają swobodne zmienianie rozmiaru aplikacji
Zachowaj stan po zmianie rozmiaru
- Utrzymuje stan interfejsu po zmianie rozmiaru za pomocą funkcji rememberSaveable.
- Unikaj niepotrzebnego powielania pracy w tle w celu zainicjowania interfejsu.
Czego potrzebujesz
- umiejętność tworzenia podstawowych aplikacji na Androida;
- Znajomość ViewModel i State w Compose
- Urządzenie testowe, które obsługuje zmianę rozmiaru okien o dowolnym kształcie, np. jedno z tych urządzeń:
- Chromebook z konfiguracją ADB
- tablet obsługujący tryb Samsung DeX lub tryb produktywności;
- emulator wirtualnego urządzenia z Androidem na komputer w Android Studio;
Jeśli podczas wykonywania tego laboratorium napotkasz jakiekolwiek problemy (błędy w kodzie, błędy gramatyczne, niejasne sformułowania itp.), zgłoś je, klikając link Zgłoś błąd w lewym dolnym rogu laboratorium.
2. Pierwsze kroki
Sklonuj repozytorium z GitHub.
git clone https://github.com/android/large-screen-codelabs/
…lub pobierz plik ZIP repozytorium i go rozpakuj.
Zaimportuj projekt
- Otwórz Android Studio
- Wybierz Importuj projekt lub Plik > Nowy > Importuj projekt.
- Przejdź do miejsca, w którym sklonowano lub wyodrębniono projekt.
- Otwórz folder zmiana rozmiaru.
- Otwórz projekt w folderze start. Zawiera kod startowy.
Wypróbuj aplikację
- Tworzenie i uruchamianie aplikacji
- Spróbuj zmienić rozmiar aplikacji
Co myślisz?
W zależności od zgodności urządzenia testowego z funkcją możesz zauważyć, że wrażenia użytkownika nie są idealne. Nie można zmienić rozmiaru aplikacji, która jest zablokowana w początkowym współczynniku proporcji. Co się dzieje?
Ograniczenia dotyczące pliku manifestu
Jeśli zajrzysz do pliku AndroidManifest.xml aplikacji, zobaczysz, że dodano kilka ograniczeń, które uniemożliwiają prawidłowe działanie aplikacji w środowisku zmiany rozmiaru okna o dowolnym kształcie.
AndroidManifest.xml
android:maxAspectRatio="1.4"
android:resizeableActivity="false"
android:screenOrientation="portrait">
Spróbuj usunąć te 3 problematyczne wiersze z pliku manifestu, ponownie skompilować aplikację i ponownie przetestować ją na urządzeniu testowym. Zauważysz, że aplikacja nie ma już ograniczeń dotyczących dowolnej zmiany rozmiaru. Usunięcie takich ograniczeń z manifestu to ważny krok w optymalizacji aplikacji pod kątem dowolnej zmiany rozmiaru okna.
3. Zmiany konfiguracji związane ze zmianą rozmiaru
Gdy rozmiar okna aplikacji zostanie zmieniony, konfiguracja aplikacji zostanie zaktualizowana. Te aktualizacje mają wpływ na Twoją aplikację. Zrozumienie ich i przewidywanie ich skutków może pomóc Ci zapewnić użytkownikom doskonałe wrażenia. Najbardziej oczywiste zmiany to szerokość i wysokość okna aplikacji, ale mają one też wpływ na proporcje i orientację.
Obserwowanie zmian konfiguracji
Aby zobaczyć te zmiany w aplikacji utworzonej za pomocą systemu widoków Androida, możesz zastąpić View.onConfigurationChanged. W Jetpack Compose mamy dostęp do LocalConfiguration.current, które jest automatycznie aktualizowane za każdym razem, gdy wywoływana jest funkcja View.onConfigurationChanged.
Aby zobaczyć te zmiany konfiguracji w przykładowej aplikacji, dodaj do niej komponent kompozycyjny, który wyświetla wartości z LocalConfiguration.current, lub utwórz nowy przykładowy projekt z takim komponentem. Przykładowy interfejs użytkownika do wyświetlania tych informacji może wyglądać tak:
val configuration = LocalConfiguration.current
val isPortrait = configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
val screenLayoutSize =
when (configuration.screenLayout and
Configuration.SCREENLAYOUT_SIZE_MASK) {
SCREENLAYOUT_SIZE_SMALL -> "SCREENLAYOUT_SIZE_SMALL"
SCREENLAYOUT_SIZE_NORMAL -> "SCREENLAYOUT_SIZE_NORMAL"
SCREENLAYOUT_SIZE_LARGE -> "SCREENLAYOUT_SIZE_LARGE"
SCREENLAYOUT_SIZE_XLARGE -> "SCREENLAYOUT_SIZE_XLARGE"
else -> "undefined value"
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text("screenWidthDp: ${configuration.screenWidthDp}")
Text("screenHeightDp: ${configuration.screenHeightDp}")
Text("smallestScreenWidthDp: ${configuration.smallestScreenWidthDp}")
Text("orientation: ${if (isPortrait) "portrait" else "landscape"}")
Text("screenLayout SIZE: $screenLayoutSize")
}
Przykładową implementację znajdziesz w folderze projektu observing-configuration-changes. Spróbuj dodać ten kod do interfejsu aplikacji, uruchomić go na urządzeniu testowym i obserwować, jak interfejs się zmienia w miarę modyfikacji konfiguracji aplikacji.

Te zmiany w konfiguracji aplikacji pozwalają szybko symulować przejście od ekstremalnych wartości, których można się spodziewać w przypadku podzielonego ekranu na małym telefonie, do pełnego ekranu na tablecie lub komputerze. To nie tylko dobry sposób na przetestowanie układu aplikacji na różnych ekranach, ale też możliwość sprawdzenia, jak dobrze radzi sobie ona z szybkimi zmianami konfiguracji.
4. Rejestrowanie zdarzeń cyklu życia działania
Kolejnym skutkiem dowolnego zmieniania rozmiaru okna aplikacji są różne zmiany w Activitycyklu życia aplikacji. Aby zobaczyć te zmiany w czasie rzeczywistym, dodaj obserwatora cyklu życia do metody onCreate i rejestruj każde nowe zdarzenie cyklu życia, zastępując metodę onStateChanged.
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
Log.d("resizing-codelab-lifecycle", "$event was called")
}
})
Po włączeniu logowania ponownie uruchom aplikację na urządzeniu testowym i sprawdź logcat, gdy będziesz minimalizować aplikację i przywracać ją na pierwszy plan.
Zauważ, że aplikacja jest wstrzymywana, gdy jest zminimalizowana, a następnie wznawiana, gdy jest przenoszona na pierwszy plan. Ma to wpływ na Twoją aplikację, co zobaczysz w następnej sekcji tego ćwiczenia, która jest poświęcona ciągłości.

Teraz sprawdź Logcat, aby zobaczyć, które wywołania zwrotne cyklu życia działania są wywoływane, gdy zmieniasz rozmiar aplikacji z najmniejszego możliwego na największy możliwy.
W zależności od urządzenia testowego możesz zaobserwować różne zachowania, ale prawdopodobnie zauważysz, że aktywność jest niszczona i tworzona ponownie, gdy rozmiar okna aplikacji zmienia się w znacznym stopniu, ale nie wtedy, gdy zmienia się nieznacznie. Dzieje się tak, ponieważ w przypadku interfejsu API w wersji 24 lub nowszej tylko znaczne zmiany rozmiaru powodują ponowne utworzenie Activity.
Poznaliśmy już niektóre typowe zmiany konfiguracji, których można się spodziewać w środowisku z okienkowaniem o dowolnym kształcie, ale warto pamiętać też o innych zmianach. Jeśli na przykład do urządzenia testowego jest podłączony monitor zewnętrzny, możesz zobaczyć, że Activity jest niszczony i tworzony ponownie, aby uwzględnić zmiany konfiguracji, takie jak gęstość wyświetlania.
Aby uprościć niektóre zmiany konfiguracji, używaj interfejsów API wyższego poziomu, takich jak WindowSizeClass, do implementowania adaptacyjnego interfejsu. (Zobacz też Obsługa różnych rozmiarów ekranu).
5. Ciągłość – zachowywanie stanu wewnętrznego komponentów kompozycyjnych po zmianie rozmiaru
W poprzedniej sekcji przedstawiliśmy niektóre zmiany konfiguracji, których możesz się spodziewać w aplikacji w środowisku z dowolną zmianą rozmiaru okna. W tej sekcji stan interfejsu aplikacji będzie zachowywany przez cały czas wprowadzania zmian.
Zacznij od rozszerzenia funkcji typu „composable” NavigationDrawerHeader (znajdującej się w ReplyHomeScreen.kt), aby po kliknięciu wyświetlała adres e-mail.
@Composable
private fun NavigationDrawerHeader(
modifier: Modifier = Modifier
) {
var showDetails by remember { mutableStateOf(false) }
Column(
modifier = modifier.clickable {
showDetails = !showDetails
}
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
ReplyLogo(
modifier = Modifier
.size(dimensionResource(R.dimen.reply_logo_size))
)
ReplyProfileImage(
drawableResource = LocalAccountsDataProvider
.userAccount.avatar,
description = stringResource(id = R.string.profile),
modifier = Modifier
.size(dimensionResource(R.dimen.profile_image_size))
)
}
AnimatedVisibility (showDetails) {
Text(
text = stringResource(id = LocalAccountsDataProvider
.userAccount.email),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.padding(
start = dimensionResource(
R.dimen.drawer_padding_header),
end = dimensionResource(
R.dimen.drawer_padding_header),
bottom = dimensionResource(
R.dimen.drawer_padding_header)
),
)
}
}
}
Po dodaniu rozwijanego nagłówka do aplikacji
- uruchomić aplikację na urządzeniu testowym,
- kliknij nagłówek, aby go rozwinąć;
- spróbuj zmienić rozmiar okna,
Zobaczysz, że nagłówek traci swój stan, gdy zostanie znacznie zmieniony.

Stan interfejsu utracił się, ponieważ remember pomaga zachować stan podczas ponownego komponowania, ale nie podczas ponownego tworzenia aktywności lub procesu. Często stosuje się przenoszenie stanu, czyli przenoszenie stanu do wywołującego funkcji kompozycyjnej, aby uczynić funkcje kompozycyjne bezstanowymi. Pozwala to całkowicie uniknąć tego problemu. Możesz jednak używać remember w miejscach, w których stan elementu interfejsu jest przechowywany w funkcjach kompozycyjnych.
Aby rozwiązać te problemy, zastąp znak remember znakiem rememberSaveable. Działa to, ponieważ rememberSaveable zapisuje i przywraca zapamiętaną wartość do savedInstanceState. Zmień remember na rememberSaveable, uruchom aplikację na urządzeniu testowym i spróbuj ponownie zmienić jej rozmiar. Zauważysz, że stan rozwijanego nagłówka jest zachowywany podczas zmiany rozmiaru, zgodnie z zamierzeniem.
6. Unikanie niepotrzebnego powielania pracy w tle
Wiesz już, jak za pomocą rememberSaveable zachować wewnętrzny stan interfejsu komponentów kompozycyjnych podczas zmian konfiguracji, które mogą często występować w wyniku dowolnego zmiany rozmiaru okna. Aplikacja powinna jednak często przenosić stan i logikę interfejsu z funkcji kompozycyjnych. Przeniesienie własności stanu do ViewModelu to jeden z najlepszych sposobów na zachowanie stanu podczas zmiany rozmiaru. Podczas przenoszenia stanu do ViewModel możesz napotkać problemy z długotrwałymi zadaniami w tle, takimi jak intensywny dostęp do systemu plików lub wywołania sieciowe, które są niezbędne do zainicjowania ekranu.
Aby zobaczyć przykład problemów, które mogą wystąpić, dodaj instrukcję logowania do metody initializeUIState w pliku ReplyViewModel.
fun initializeUIState() {
Log.d("resizing-codelab", "initializeUIState() called in the viewmodel")
val mailboxes: Map<MailboxType, List<Email>> =
LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
_uiState.value =
ReplyUiState(
mailboxes = mailboxes,
currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
?: LocalEmailsDataProvider.defaultEmail
)
}
Uruchom aplikację na urządzeniu testowym i kilka razy zmień rozmiar jej okna.
Gdy sprawdzisz Logcat, zobaczysz, że metoda inicjowania została uruchomiona kilka razy. Może to być problem w przypadku zadań, które chcesz uruchomić tylko raz, aby zainicjować interfejs. Dodatkowe wywołania sieciowe, operacje wejścia/wyjścia plików lub inne działania mogą pogorszyć wydajność urządzenia i spowodować inne niepożądane problemy.
Aby uniknąć niepotrzebnej pracy w tle, usuń wywołanie funkcji initializeUIState() z metody onCreate() aktywności. Zamiast tego zainicjuj dane w metodzie init klasy ViewModel. Dzięki temu metoda inicjowania jest uruchamiana tylko raz, gdy po raz pierwszy tworzona jest instancja ReplyViewModel:
init {
initializeUIState()
}
Spróbuj ponownie uruchomić aplikację. Zobaczysz, że niepotrzebne symulowane zadanie inicjowania jest wykonywane tylko raz, niezależnie od tego, ile razy zmienisz rozmiar okna aplikacji. Dzieje się tak, ponieważ obiekty ViewModel są zachowywane po zakończeniu cyklu życia Activity. Uruchamiając kod inicjujący tylko raz podczas tworzenia ViewModel, oddzielamy go od wszelkich ponownych tworzeń Activity i zapobiegamy niepotrzebnej pracy. Gdyby to było kosztowne wywołanie serwera lub operacja wejścia/wyjścia dużego pliku w celu zainicjowania interfejsu, zaoszczędzisz znaczne zasoby i poprawisz komfort użytkownika.
7. GRATULACJE!
Udało się! Dobra robota! Wdrożono już niektóre sprawdzone metody umożliwiające prawidłowe skalowanie aplikacji na Androida w ChromeOS i innych środowiskach z wieloma oknami i ekranami.
Przykładowy kod źródłowy
Klonowanie repozytorium z GitHuba
git clone https://github.com/android/large-screen-codelabs/
…lub pobierz plik ZIP repozytorium i go rozpakuj.