1. Zanim zaczniesz
Co wyróżnia urządzenia składane?
Urządzenia składane to innowacyjne urządzenia dostępne raz w tej samej generacji. Zapewniają one wyjątkowe wrażenia, a dzięki nim użytkownicy mogą korzystać z różnych funkcji, takich jak interfejs stołu do obsługi bez użycia rąk.
Wymagania wstępne
- Podstawowa wiedza o tworzeniu aplikacji na Androida
- Podstawowa znajomość platformy Hilt Dependency Injection
Co utworzysz
W ramach tego ćwiczenia w programie utworzysz aplikację aparatu ze zoptymalizowanymi układami pod kątem urządzeń składanych.
Zaczniesz od podstawowej aplikacji do robienia zdjęć, która nie reaguje na żadne ustawienie urządzenia ani nie wykorzystuje lepszego tylnego aparatu do robienia lepszych selfie. Musisz zaktualizować kod źródłowy, aby po rozłożeniu urządzenia przenieść podgląd na mniejszy wyświetlacz. Reaguje on na telefon ustawiony w trybie Na stole.
Chociaż najwygodniejszym przypadkiem użycia tego interfejsu API jest aplikacja aparatu, obie funkcje podane w tym ćwiczeniu z programowania możesz zastosować w dowolnej aplikacji.
Czego się nauczysz
- Jak używać aplikacji Jetpack Window Manager do reagowania na zmianę stanu
- Jak przenieść aplikację na mniejszy ekran urządzenia składanego
Czego potrzebujesz
- najnowszą wersję Android Studio,
- Urządzenie składane lub składany emulator
2. Konfiguracja
Pobieranie kodu startowego
- Jeśli masz zainstalowany Git, możesz po prostu uruchomić poniższe polecenie. Aby sprawdzić, czy Git jest zainstalowany, wpisz
git --version
w terminalu lub wierszu poleceń i sprawdź, czy program uruchamia się poprawnie.
git clone https://github.com/android/large-screen-codelabs.git
- Opcjonalnie: jeśli nie masz Git, możesz kliknąć ten przycisk, aby pobrać cały kod do tego ćwiczenia w programowaniu:
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ć format obrazu. Jednak pierwszy przycisk po lewej stronie obecnie nic nie robi, ale służy do włączenia trybu tylnego selfie.
- Teraz spróbuj umieścić urządzenie w połowie otwartego położenia, w którym zawias nie będzie całkowicie płaski ani zamknięty, ale będzie pod kątem 90 stopni.
Jak widać, aplikacja nie reaguje na różne ustawienia urządzenia, więc układ się nie zmienia, przez co zawias pozostaje pośrodku wizjera.
4. Więcej informacji o Jetpack WindowManager
Biblioteka Jetpack WindowManager pomaga deweloperom aplikacji tworzyć zoptymalizowane pod kątem urządzeń składanych. Zawiera klasę FoldingFeature
opisującą sposób zawinięcia w elastycznym wyświetlaczu lub zawias między 2 fizycznymi panelami wyświetlacza. 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_OPENED
w innym przypadku.orientation()
zwracaFoldingFeature.Orientation.HORIZONTAL
, jeśli szerokośćFoldingFeature
jest większa niż wysokość; w przeciwnym razie zwracaFoldingFeature.Orientation.VERTICAL
.bounds()
podaje graniceFoldingFeature
w formacieRect
.
Klasa FoldingFeature
zawiera dodatkowe informacje, takie jak occlusionType()
czy isSeparating()
, ale to ćwiczenia w Codelabs nie są szczegółowo omówione.
Począwszy od wersji 1.2.0-beta01, biblioteka używa WindowAreaController
– interfejsu API, który umożliwia trybowi tylnego wyświetlacza przeniesienie bieżącego okna na wyświetlacz dopasowany do tylnego aparatu, co świetnie nadaje się do robienia selfie tylnym aparatem i w wielu innych sytuacjach.
Dodaj zależności
- Aby używać w aplikacji Jetpack WindowManager, musisz do pliku
build.gradle
na poziomie modułu dodać te zależności:
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"
W swojej aplikacji masz teraz dostęp zarówno do zajęć FoldingFeature
, jak i do zajęć WindowAreaController
. Wykorzystują je do tworzenia składanego aparatu.
5. Wdrażanie trybu tylnego selfie
Włącz tryb tylnego wyświetlacza.
Ten tryb zapewnia interfejs API WindowAreaController
, który udostępnia informacje i zachowania dotyczące przenoszenia okien między wyświetlaczami lub obszarami wyświetlania na urządzeniu.
Umożliwia wysyłanie zapytań o listy WindowAreaInfo
, które są obecnie dostępne do interakcji.
Za pomocą WindowAreaInfo
możesz uzyskać dostęp do interfejsu WindowAreaSession
, który reprezentuje funkcję obszaru aktywnych okien i stan dostępności określonych elementów WindowAreaCapability.
.
- Zadeklaruj te zmienne w
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
- I 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 zależności od bieżącego stanu włączyć lub wyłączyć przycisk tylnego selfie:
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
}
}
}
}
Ostatni krok jest opcjonalny, ale bardzo przydatny jest poznanie wszystkich możliwych stanów WindowAreaCapability.
- Teraz zaimplementuj funkcję
toggleRearDisplayMode
, która spowoduje zamknięcie sesji, jeśli jest już aktywna, lub wywołaj 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 to, że MainActivity
to element typu WindowAreaSessionCallback
.
Interfejs Rear Display API współpracuje z detektorem: gdy prosisz o przeniesienie treści na drugi wyświetlacz, inicjujesz sesję, która jest zwracana za pomocą metody onSessionStarted()
detektora. Gdy zamiast tego chcesz wrócić do wewnętrznego (i większego) wyświetlacza, zamkniesz sesję, a w metodzie onSessionEnded()
pojawi się potwierdzenie. Aby utworzyć taki detektor, musisz wdrożyć interfejs WindowAreaSessionCallback
.
- Zmodyfikuj deklarację
MainActivity
, tak aby implementowała interfejsWindowAreaSessionCallback
:
step1/MainActivity.kt
class MainActivity : AppCompatActivity(), WindowAreaSessionCallback
Teraz zaimplementuj metody onSessionStarted
i onSessionEnded
w obiekcie MainActivity
. Te metody wywołania zwrotnego są bardzo przydatne, gdy chcesz otrzymywać powiadomienia o stanie sesji i odpowiednio aktualizować aplikację.
Jednak tym razem dla uproszczenia sprawdź, czy w treści funkcji są jakieś błędy, i zapisz 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]")
}
- Skompilować i uruchomić aplikację. Gdy otworzysz urządzenie i naciśniesz przycisk tylnego wyświetlacza, pojawi się komunikat podobny do tego:
- Wybierz „Zmień ekran teraz”, aby zobaczyć treści przeniesione na wyświetlacz zewnętrzny.
6. Wdrażanie trybu Na stole
Czas dostosować aplikację do elementów, które są składane na dwa sposoby: możesz przesuwać zawartość z boku lub nad zawias urządzenia w zależności od tego, w jaki sposób się znajduje. W tym celu należy wykonać czynności wewnątrz obiektu FoldingStateActor
, w ten sposób odłączając kod od pola Activity
, co ułatwi jego czytelność.
Głównym elementem tego interfejsu API jest interfejs WindowInfoTracker
, który jest utworzony za pomocą metody statycznej, która wymaga Activity
:
step1/CameraCodelabDependencies.kt
@Provides
fun provideWindowInfoTracker(activity: Activity) =
WindowInfoTracker.getOrCreate(activity)
Nie musisz pisać tego kodu, ponieważ on już występuje. Warto jednak zrozumieć, jak jest zbudowany WindowInfoTracker
.
- Aby wykryć ewentualne zmiany w oknach, posłuchaj tych zmian w metodzie
onResume()
na urządzeniuActivity
:
step1/MainActivity.kt
lifecycleScope.launch {
foldingStateActor.checkFoldingState(
this@MainActivity,
binding.viewFinder
)
}
- Teraz otwórz plik
FoldingStateActor
. Aby to zrobić, trzeba wypełnić metodęcheckFoldingState()
.
Jak już wiesz, działa w fazie RESUMED
Twojego elementu (Activity
) i wykorzystuje WindowInfoTracker
, aby wykrywać wszelkie zmiany układu.
step1/FoldingStateActor.kt
windowInfoTracker.windowLayoutInfo(activity)
.collect { newLayoutInfo ->
activeWindowLayoutInfo = newLayoutInfo
updateLayoutByFoldingState(cameraViewfinder)
}
W interfejsie WindowInfoTracker
możesz wywołać windowLayoutInfo()
, aby zebrać Flow
z WindowLayoutInfo
obejmujące wszystkie informacje dostępne w narzędziu DisplayFeature
.
Ostatnim krokiem jest reagowanie na te zmiany i odpowiednie przenoszenie treści. Robisz to w metodzie updateLayoutByFoldingState()
, krok po kroku.
- Upewnij się, że obiekt
activityLayoutInfo
zawiera właściwościDisplayFeature
i że co najmniej jedna z nich toFoldingFeature
. W przeciwnym razie nie musisz nic robić:
step1/FoldingStateActor.kt
val foldingFeature = activeWindowLayoutInfo?.displayFeatures
?.firstOrNull { it is FoldingFeature } as FoldingFeature?
?: return
- Oblicz pozycję części strony widocznej na ekranie, by mieć pewność, że to urządzenie wpływa na układ i nie wykracza poza granice Twojej hierarchii:
step1/FoldingStateActor.kt
val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
foldingFeature,
cameraViewfinder.parent as View
) ?: return
Teraz masz pewność, że masz element FoldingFeature
, który wpływa na układ, więc musisz przenieść treść.
- Sprawdź, czy
FoldingFeature
toHALF_OPEN
, czy po prostu przywrócisz treść. Jeśli toHALF_OPEN
, musisz ponownie przeprowadzić weryfikację i postępować w zależności od orientacji części strony widocznej na ekranie:
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 część strony widoczna na ekranie ma długość VERTICAL
, przesuwasz treść w prawo, a w przeciwnym razie – na część strony widoczną na ekranie.
- Utwórz i uruchom aplikację, a następnie otwórz urządzenie i włącz na nim tryb stołu, żeby zobaczyć, jak zmienia się treść.
7. Gratulacje!
Dzięki temu ćwiczeniu w Codelabs omówiliśmy niektóre funkcje dostępne tylko na urządzeniach składanych, takie jak tryb tylnego wyświetlacza i tryb Na stole. Poznaliśmy też sposoby odblokowywania tych urządzeń za pomocą aplikacji Jetpack WindowManager.
Możesz już wdrożyć wygodę użytkowników w swojej aplikacji aparatu.