Odkryj funkcje kamery

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.

6caebc2739522a1b.png

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

  1. 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
  1. 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.

Zrzut ekranu przedstawiający Android Studio, na którym widać kod związany z tym ćwiczeniem z programowania

Jeśli pojawi się prośba o użycie najnowszej wersji Gradle, zaktualizuj ją.

3. Uruchom i obserwuj

  1. 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.

a34aca632d75aa09.png

  1. 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:

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..

  1. 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
  1. 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()
      }
  }
}
  1. 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.

  1. 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.

  1. Zmodyfikuj deklarację MainActivity, tak aby implementowała interfejs WindowAreaSessionCallback:

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]")
}
  1. Skompilować i uruchomić aplikację. Gdy otworzysz urządzenie i naciśniesz przycisk tylnego wyświetlacza, pojawi się komunikat podobny do tego:

3fa50cce0b0d4b8d.png

  1. 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.

  1. Aby wykryć ewentualne zmiany w oknach, posłuchaj tych zmian w metodzie onResume() na urządzeniu Activity:

step1/MainActivity.kt

lifecycleScope.launch {
    foldingStateActor.checkFoldingState(
         this@MainActivity, 
         binding.viewFinder
    )
}
  1. 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.

  1. Upewnij się, że obiekt activityLayoutInfo zawiera właściwości DisplayFeature i że co najmniej jedna z nich to FoldingFeature. W przeciwnym razie nie musisz nic robić:

step1/FoldingStateActor.kt

val foldingFeature = activeWindowLayoutInfo?.displayFeatures
            ?.firstOrNull { it is FoldingFeature } as FoldingFeature?
            ?: return
  1. 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ść.

  1. Sprawdź, czy FoldingFeature to HALF_OPEN, czy po prostu przywrócisz treść. Jeśli to HALF_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.

  1. 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.

Więcej informacji

Produkty