1. Zanim zaczniesz
Co jest wyjątkowego w telefonach składanych?
Urządzenia składane to innowacje, które pojawiają się raz na pokolenie. Zapewniają one wyjątkowe wrażenia, a tym samym wyjątkowe możliwości zachwycenia użytkowników wyróżniającymi się funkcjami, takimi jak interfejs stołowy do obsługi bez użycia rąk.
Wymagania wstępne
- Podstawowa wiedza na temat tworzenia aplikacji na Androida
- Podstawowa wiedza o platformie wstrzykiwania zależności Hilt
Co utworzysz
W tym module utworzysz aplikację aparatu ze zoptymalizowanymi układami na urządzenia składane.

Zaczynasz od podstawowej aplikacji aparatu, która nie reaguje na żadną pozycję urządzenia ani nie wykorzystuje lepszego tylnego aparatu do robienia lepszych selfie. Aktualizujesz kod źródłowy, aby przenieść podgląd na mniejszy wyświetlacz po rozłożeniu urządzenia i reagować na ustawienie telefonu w trybie na stole.
Aplikacja aparatu to najwygodniejszy przypadek użycia tego interfejsu API, ale obie funkcje, których nauczysz się w tym laboratorium, można zastosować w dowolnej aplikacji.
Czego się nauczysz
- Jak używać Jetpack Window Manager do reagowania na zmiany pozycji
- Jak przenieść aplikację na mniejszy wyświetlacz urządzenia składanego
Czego potrzebujesz
- Najnowsza wersja Androida Studio
- składane urządzenie lub emulator składanego urządzenia;
2. Konfiguracja
Pobieranie kodu początkowego
- Jeśli masz zainstalowany Git, możesz po prostu uruchomić to polecenie. Aby sprawdzić, czy Git jest zainstalowany, wpisz
git --versionw terminalu lub wierszu poleceń i sprawdź, czy polecenie zostało wykonane prawidłowo.
git clone https://github.com/android/large-screen-codelabs.git
- Opcjonalnie: jeśli nie masz Gita, możesz kliknąć ten przycisk, aby pobrać cały kod do tego ćwiczenia:
Otwórz pierwszy moduł
- W Android Studio otwórz pierwszy moduł w sekcji
/step1.

Jeśli pojawi się prośba o użycie najnowszej wersji Gradle, zaktualizuj ją.
3. Uruchom i obserwuj
- Uruchom kod w module
step1.
Jak widać, jest to prosta aplikacja aparatu. Możesz przełączać się między przednim a tylnym aparatem i dostosowywać proporcje obrazu. Pierwszy przycisk z lewej strony nie robi obecnie nic, ale będzie punktem wejścia do trybu tylnego selfie.

- Teraz spróbuj ustawić urządzenie w położeniu półotwartym, w którym zawias nie jest całkowicie płaski ani zamknięty, ale tworzy kąt 90 stopni.
Jak widać, aplikacja nie reaguje na różne pozycje urządzenia, więc układ się nie zmienia, a zawias pozostaje na środku wizjera.
4. Więcej informacji o bibliotece Jetpack WindowManager
Biblioteka Jetpack WindowManager pomaga programistom tworzyć aplikacje zoptymalizowane pod kątem urządzeń składanych. Zawiera klasę FoldingFeature, która opisuje zagięcie na elastycznym wyświetlaczu lub zawias między dwoma fizycznymi panelami wyświetlacza. Jego interfejs API zapewnia dostęp do ważnych informacji związanych z urządzeniem:
state()zwracaFLAT, jeśli zawias jest otwarty pod kątem 180 stopni, lubHALF_OPENEDw innych przypadkach.orientation()zwracaFoldingFeature.Orientation.HORIZONTAL, jeśli szerokośćFoldingFeaturejest większa niż wysokość; w przeciwnym razie zwracaFoldingFeature.Orientation.VERTICAL.bounds()podaje graniceFoldingFeaturew formacieRect.
Klasa FoldingFeature zawiera dodatkowe informacje, takie jak occlusionType() czy isSeparating(), ale w tym laboratorium nie będziemy ich szczegółowo omawiać.
Od wersji 1.2.0-beta01 biblioteka używa interfejsu WindowAreaController, który umożliwia przeniesienie bieżącego okna na wyświetlacz wyrównany z tylnym aparatem. Jest to przydatne podczas robienia selfie tylnym aparatem i w wielu innych przypadkach.
Dodawanie zależności
- Aby używać Jetpack WindowManager w aplikacji, musisz dodać te zależności do pliku
build.gradlena poziomie modułu:
step1/build.gradle
def work_version = '1.2.0-beta01'
implementation "androidx.window:window:$work_version"
implementation "androidx.window:window-java:$work_version"
implementation "androidx.window:window-core:$work_version"
Teraz w aplikacji możesz korzystać z klas FoldingFeature i WindowAreaController. Użyj ich, aby stworzyć najlepsze rozwiązanie do obsługi aparatu na urządzeniach składanych.
5. Wdrażanie trybu selfie z tylnego aparatu
Zacznij od trybu tylnego wyświetlacza.
Interfejsem API, który umożliwia ten tryb, jest WindowAreaController. Zapewnia on informacje i działanie związane z przenoszeniem okien między wyświetlaczami lub obszarami wyświetlania na urządzeniu.
Umożliwia wysyłanie zapytań dotyczących listy WindowAreaInfo, z którymi można obecnie wchodzić w interakcje.
Za pomocą WindowAreaInfo możesz uzyskać dostęp do WindowAreaSession, czyli interfejsu reprezentującego aktywny obszar okna i stan dostępności określonego WindowAreaCapability..
- Zadeklaruj te zmienne w pliku
MainActivity:
step1/MainActivity.kt
private lateinit var windowAreaController: WindowAreaController
private lateinit var displayExecutor: Executor
private var rearDisplaySession: WindowAreaSession? = null
private var rearDisplayWindowAreaInfo: WindowAreaInfo? = null
private var rearDisplayStatus: WindowAreaCapability.Status =
WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED
private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
- Zainicjuj je w metodzie
onCreate():
step1/MainActivity.kt
displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaController.getOrCreate()
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
windowAreaController.windowAreaInfos
.map{info->info.firstOrNull{it.type==WindowAreaInfo.Type.TYPE_REAR_FACING}}
.onEach { info -> rearDisplayWindowAreaInfo = info }
.map{it?.getCapability(rearDisplayOperation)?.status?: WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED }
.distinctUntilChanged()
.collect {
rearDisplayStatus = it
updateUI()
}
}
}
- Teraz zaimplementuj funkcję
updateUI(), aby włączyć lub wyłączyć przycisk do robienia selfie tylnym aparatem w zależności od bieżącego stanu:
step1/MainActivity.kt
private fun updateUI() {
if(rearDisplaySession != null) {
binding.rearDisplay.isEnabled = true
// A session is already active, clicking on the button will disable it
} else {
when(rearDisplayStatus) {
WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> {
binding.rearDisplay.isEnabled = false
// RearDisplay Mode is not supported on this device"
}
WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> {
binding.rearDisplay.isEnabled = false
// RearDisplay Mode is not currently available
}
WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> {
binding.rearDisplay.isEnabled = true
// You can enable RearDisplay Mode
}
WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> {
binding.rearDisplay.isEnabled = true
// You can disable RearDisplay Mode
}
else -> {
binding.rearDisplay.isEnabled = false
// RearDisplay status is unknown
}
}
}
}
Ten ostatni krok jest opcjonalny, ale warto poznać wszystkie możliwe stany WindowAreaCapability.
- Teraz zaimplementuj funkcję
toggleRearDisplayMode, która zamknie sesję, jeśli funkcja jest już aktywna, lub wywoła funkcjętransferActivityToWindowArea:
step1/CameraViewModel.kt
private fun toggleRearDisplayMode() {
if(rearDisplayStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) {
if(rearDisplaySession == null) {
rearDisplaySession = rearDisplayWindowAreaInfo?.getActiveSession(rearDisplayOperation)
}
rearDisplaySession?.close()
} else {
rearDisplayWindowAreaInfo?.token?.let { token ->
windowAreaController.transferActivityToWindowArea(
token = token,
activity = this,
executor = displayExecutor,
windowAreaSessionCallback = this
)
}
}
}
Zwróć uwagę na użycie znaku MainActivity jako WindowAreaSessionCallback.
Interfejs Rear Display API działa na zasadzie detektora: gdy poprosisz o przeniesienie treści na inny wyświetlacz, zainicjujesz sesję, która zostanie zwrócona za pomocą metody onSessionStarted() detektora. Jeśli chcesz wrócić do wewnętrznego (i większego) wyświetlacza, zamknij sesję. Potwierdzenie otrzymasz w onSessionEnded(). Aby utworzyć taki detektor, musisz zaimplementować interfejs WindowAreaSessionCallback.
- Zmodyfikuj deklarację
MainActivity, aby implementowała interfejsWindowAreaSessionCallback:
step1/MainActivity.kt
class MainActivity : AppCompatActivity(), WindowAreaSessionCallback
Teraz zaimplementuj metody onSessionStarted i onSessionEnded w klasie MainActivity. Te metody wywołania zwrotnego są bardzo przydatne do otrzymywania powiadomień o stanie sesji i odpowiedniego aktualizowania aplikacji.
Tym razem jednak dla uproszczenia sprawdź w treści funkcji, czy występują jakieś błędy, i zarejestruj stan.
step1/MainActivity.kt
override fun onSessionEnded(t: Throwable?) {
if(t != null) {
Log.d("Something was broken: ${t.message}")
}else{
Log.d("rear session ended")
}
}
override fun onSessionStarted(session: WindowAreaSession) {
Log.d("rear session started [session=$session]")
}
- Skompiluj i uruchom aplikację. Jeśli następnie rozłożysz urządzenie i klikniesz przycisk tylnego wyświetlacza, pojawi się komunikat podobny do tego:

- Kliknij „Przełącz ekrany”, aby przenieść treści na zewnętrzny wyświetlacz.
6. Wdrażanie trybu na stole
Teraz możesz dostosować aplikację do urządzenia składanego: przenieś treści na bok lub nad zawias urządzenia w zależności od orientacji urządzenia. W tym celu będziesz działać w FoldingStateActor, aby oddzielić kod od Activity i ułatwić jego odczytanie.
Główną częścią tego interfejsu API jest interfejs WindowInfoTracker, który jest tworzony za pomocą metody statycznej wymagającej Activity:
step1/CameraCodelabDependencies.kt
@Provides
fun provideWindowInfoTracker(activity: Activity) =
WindowInfoTracker.getOrCreate(activity)
Nie musisz pisać tego kodu, ponieważ jest on już obecny, ale warto wiedzieć, jak jest zbudowany element WindowInfoTracker.
- Aby monitorować zmiany okna, nasłuchuj tych zmian w metodzie
onResume()obiektuActivity:
step1/MainActivity.kt
lifecycleScope.launch {
foldingStateActor.checkFoldingState(
this@MainActivity,
binding.viewFinder
)
}
- Teraz otwórz plik
FoldingStateActor, ponieważ nadszedł czas na wypełnienie metodycheckFoldingState().
Jak już wiesz, działa on w fazie RESUMED komponentu Activity i wykorzystuje WindowInfoTracker do monitorowania wszelkich zmian układu.
step1/FoldingStateActor.kt
windowInfoTracker.windowLayoutInfo(activity)
.collect { newLayoutInfo ->
activeWindowLayoutInfo = newLayoutInfo
updateLayoutByFoldingState(cameraViewfinder)
}
Korzystając z interfejsu WindowInfoTracker, możesz wywołać windowLayoutInfo(), aby zebrać Flow WindowLayoutInfo zawierający wszystkie dostępne informacje w DisplayFeature.
Ostatnim krokiem jest zareagowanie na te zmiany i odpowiednie przeniesienie treści. Robisz to w metodzie updateLayoutByFoldingState(), krok po kroku.
- Sprawdź, czy element
activityLayoutInfozawiera właściwościDisplayFeaturei czy co najmniej jedna z nich jest typuFoldingFeature. W przeciwnym razie nie wykonuj żadnych działań:
step1/FoldingStateActor.kt
val foldingFeature = activeWindowLayoutInfo?.displayFeatures
?.firstOrNull { it is FoldingFeature } as FoldingFeature?
?: return
- Oblicz położenie zagięcia, aby upewnić się, że położenie urządzenia wpływa na układ i nie wykracza poza granice hierarchii:
step1/FoldingStateActor.kt
val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
foldingFeature,
cameraViewfinder.parent as View
) ?: return
Teraz masz pewność, że masz FoldingFeature, który wpływa na układ, więc musisz przenieść treści.
- Sprawdź, czy
FoldingFeaturema wartośćHALF_OPEN, w przeciwnym razie przywróć pozycję treści. Jeśli jest toHALF_OPEN, musisz przeprowadzić kolejne sprawdzenie i podjąć inne działania w zależności od orientacji urządzenia po rozłożeniu:
step1/FoldingStateActor.kt
if (foldingFeature.state == FoldingFeature.State.HALF_OPENED) {
when (foldingFeature.orientation) {
FoldingFeature.Orientation.VERTICAL -> {
cameraViewfinder.moveToRightOf(foldPosition)
}
FoldingFeature.Orientation.HORIZONTAL -> {
cameraViewfinder.moveToTopOf(foldPosition)
}
}
} else {
cameraViewfinder.restore()
}
Jeśli zagięcie jest w pozycji VERTICAL, przesuń treści w prawo, w przeciwnym razie przesuń je na pozycję zagięcia.
- Zbuduj i uruchom aplikację, a potem otwórz urządzenie i umieść je w trybie stołu, aby zobaczyć, jak treść się przesuwa.
7. Gratulacje!
Z tego laboratorium dowiedziałeś się o niektórych funkcjach, które są dostępne tylko na urządzeniach składanych, takich jak tryb tylnego wyświetlacza czy tryb na stole, oraz o tym, jak je odblokować za pomocą biblioteki Jetpack WindowManager.
Możesz już wdrożyć w aplikacji aparatu funkcje, które zapewnią użytkownikom doskonałe wrażenia.