Kamera aufklappen

1. Hinweis

Was ist das Besondere an faltbaren Smartphones?

Foldables sind eine einmalige Innovation. Sie bieten einzigartige Möglichkeiten, Nutzer mit differenzierten Funktionen wie der Benutzeroberfläche für die Nutzung auf dem Tisch zu begeistern.

Voraussetzungen

  • Grundkenntnisse in der Entwicklung von Android-Apps
  • Grundkenntnisse des Hilt-Frameworks für die Abhängigkeitsinjektion

Umfang

In diesem Codelab erstellen Sie eine Kamera-App mit optimierten Layouts für faltbare Geräte.

6caebc2739522a1b.png

Sie beginnen mit einer einfachen Kamera-App, die nicht auf die Geräteposition reagiert und die bessere Rückkamera nicht für optimierte Selfies nutzt. Sie aktualisieren den Quellcode, um die Vorschau auf das kleinere Display zu verschieben, wenn das Gerät aufgeklappt wird, und darauf zu reagieren, wenn das Smartphone im Tischmodus verwendet wird.

Die Kamera-App ist zwar der bequemste Anwendungsfall für diese API, aber beide Funktionen, die Sie in diesem Codelab kennenlernen, können auf jede App angewendet werden.

Lerninhalte

  • Mit Jetpack Window Manager auf Änderungen der Gerätehaltung reagieren
  • App auf das kleinere Display eines faltbaren Geräts verschieben

Voraussetzungen

  • Eine aktuelle Version von Android Studio
  • Ein faltbares Gerät oder ein Emulator für faltbare Geräte

2. Einrichten

Startcode abrufen

  1. Wenn Sie Git installiert haben, können Sie einfach den folgenden Befehl ausführen. Wenn Sie prüfen möchten, ob Git installiert ist, geben Sie git --version im Terminal oder in der Befehlszeile ein und prüfen Sie, ob der Befehl korrekt ausgeführt wird.
git clone https://github.com/android/large-screen-codelabs.git
  1. Optional: Wenn Sie Git nicht haben, können Sie auf die folgende Schaltfläche klicken, um den gesamten Code für dieses Codelab herunterzuladen:

Erstes Modul öffnen

  • Öffnen Sie in Android Studio das erste Modul unter /step1.

Screenshot von Android Studio mit dem Code für dieses Codelab

Wenn Sie aufgefordert werden, die neueste Gradle-Version zu verwenden, aktualisieren Sie sie.

3. Ausführen und beobachten

  1. Führen Sie den Code im Modul step1 aus.

Wie Sie sehen, ist dies eine einfache Kamera-App. Sie können zwischen der Front- und der Rückkamera wechseln und das Seitenverhältnis anpassen. Die erste Schaltfläche von links hat derzeit keine Funktion, wird aber der Einstiegspunkt für den Modus Selfie mit Rückkamera sein.

a34aca632d75aa09.png

  1. Versuche nun, das Gerät in eine halb geöffnete Position zu bringen, in der das Scharnier nicht vollständig flach oder geschlossen ist, sondern einen 90‑Grad-Winkel bildet.

Wie Sie sehen, reagiert die App nicht auf verschiedene Gerätepositionen. Das Layout ändert sich also nicht und das Scharnier befindet sich in der Mitte des Suchers.

4. Informationen zu Jetpack WindowManager

Die Jetpack WindowManager-Bibliothek hilft App-Entwicklern, optimierte Funktionen für faltbare Geräte zu entwickeln. Sie enthält die Klasse FoldingFeature, die eine Faltung auf einem flexiblen Display oder ein Scharnier zwischen zwei physischen Display-Panels beschreibt. Über die API können Sie auf wichtige Informationen zum Gerät zugreifen:

Die Klasse FoldingFeature enthält zusätzliche Informationen wie occlusionType() oder isSeparating(), die in diesem Codelab jedoch nicht näher erläutert werden.

Ab Version 1.2.0-beta01 verwendet die Bibliothek die WindowAreaController. Mit dieser API kann das aktuelle Fenster auf das Display verschoben werden, das auf die Rückkamera ausgerichtet ist. Das ist ideal für Selfies mit der Rückkamera und viele andere Anwendungsfälle.

Abhängigkeiten hinzufügen

  • Wenn Sie Jetpack WindowManager in Ihrer App verwenden möchten, müssen Sie die folgenden Abhängigkeiten in die Datei build.gradle auf Modulebene einfügen:

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"

Jetzt können Sie in Ihrer App sowohl auf die Klassen FoldingFeature als auch auf WindowAreaController zugreifen. Damit können Sie die ultimative Kamerafunktion für Faltgeräte entwickeln.

5. Modus für Rückkamera-Selfies implementieren

Beginnen Sie mit dem Rückdisplay-Modus.

Die API, die diesen Modus ermöglicht, ist die WindowAreaController. Sie stellt die Informationen und das Verhalten zum Verschieben von Fenstern zwischen Displays oder Displaybereichen auf einem Gerät bereit.

Damit können Sie die Liste der WindowAreaInfo abfragen, mit denen derzeit interagiert werden kann.

Mit WindowAreaInfo können Sie auf WindowAreaSession zugreifen, eine Schnittstelle zur Darstellung eines aktiven Fensterbereichs und des Verfügbarkeitsstatus für ein bestimmtes WindowAreaCapability..

  1. Deklarieren Sie diese Variablen in Ihrem 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. Initialisieren Sie sie in der Methode 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. Implementieren Sie nun die Funktion updateUI(), um die Selfie-Taste auf der Rückseite je nach aktuellem Status zu aktivieren oder zu deaktivieren:

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

Dieser letzte Schritt ist optional, aber sehr nützlich, um alle möglichen Status eines WindowAreaCapability. kennenzulernen.

  1. Implementieren Sie nun die Funktion toggleRearDisplayMode, die die Sitzung schließt, wenn die Funktion bereits aktiv ist, oder die Funktion transferActivityToWindowArea aufruft:

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
            )
        }
    }
}

Beachten Sie die Verwendung von MainActivity als WindowAreaSessionCallback.

Die Rear Display API funktioniert mit einem Listener-Ansatz: Wenn Sie anfordern, dass der Inhalt auf das andere Display verschoben wird, starten Sie eine Sitzung, die über die Methode onSessionStarted() des Listeners zurückgegeben wird. Wenn Sie stattdessen zum inneren (und größeren) Display zurückkehren möchten, schließen Sie die Sitzung. Sie erhalten dann eine Bestätigung in der onSessionEnded()-Methode. Um einen solchen Listener zu erstellen, müssen Sie die WindowAreaSessionCallback-Schnittstelle implementieren.

  1. Ändern Sie die MainActivity-Deklaration so, dass sie die WindowAreaSessionCallback-Schnittstelle implementiert:

step1/MainActivity.kt

class MainActivity : AppCompatActivity(), WindowAreaSessionCallback

Implementieren Sie nun die Methoden onSessionStarted und onSessionEnded in der MainActivity. Diese Callback-Methoden sind äußerst nützlich, um über den Sitzungsstatus benachrichtigt zu werden und die App entsprechend zu aktualisieren.

Dieses Mal prüfen wir der Einfachheit halber nur im Funktionskörper, ob Fehler vorhanden sind, und protokollieren den Status.

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. Erstelle die App und führe sie aus. Wenn du das Gerät dann aufklappst und auf die Taste für das Rückdisplay tippst, wird eine Meldung wie diese angezeigt:

3fa50cce0b0d4b8d.png

  1. Wählen Sie Jetzt wechseln aus, um die Inhalte auf dem äußeren Display anzeigen zu lassen.

6. Modus „Auf dem Tisch“ implementieren

Jetzt ist es an der Zeit, deine App für Faltgeräte zu optimieren. Je nach Ausrichtung des Faltgeräts verschiebst du deine Inhalte an die Seite oder über das Scharnier des Geräts. Dazu arbeiten Sie innerhalb von FoldingStateActor, damit Ihr Code zur besseren Lesbarkeit von Activity entkoppelt wird.

Der Kern dieser API besteht aus der Schnittstelle WindowInfoTracker, die mit einer statischen Methode erstellt wird, für die ein Activity erforderlich ist:

step1/CameraCodelabDependencies.kt

@Provides
fun provideWindowInfoTracker(activity: Activity) =
        WindowInfoTracker.getOrCreate(activity)

Sie müssen diesen Code nicht schreiben, da er bereits vorhanden ist. Es ist jedoch nützlich zu verstehen, wie WindowInfoTracker erstellt wird.

  1. Wenn Sie auf Fensteränderungen reagieren möchten, müssen Sie in der onResume()-Methode Ihres Activity auf diese Änderungen reagieren:

step1/MainActivity.kt

lifecycleScope.launch {
    foldingStateActor.checkFoldingState(
         this@MainActivity, 
         binding.viewFinder
    )
}
  1. Öffnen Sie nun die Datei FoldingStateActor, da es an der Zeit ist, die Methode checkFoldingState() auszufüllen.

Wie Sie bereits gesehen haben, wird sie in der Phase RESUMED Ihres Activity ausgeführt und nutzt WindowInfoTracker, um auf Layoutänderungen zu reagieren.

step1/FoldingStateActor.kt

windowInfoTracker.windowLayoutInfo(activity)
      .collect { newLayoutInfo ->
         activeWindowLayoutInfo = newLayoutInfo
         updateLayoutByFoldingState(cameraViewfinder)
      }

Über die WindowInfoTracker-Schnittstelle können Sie windowLayoutInfo() aufrufen, um eine Flow von WindowLayoutInfo zu erfassen, die alle verfügbaren Informationen in DisplayFeature enthält.

Im letzten Schritt müssen Sie auf diese Änderungen reagieren und die Inhalte entsprechend verschieben. Das erfolgt in der Methode updateLayoutByFoldingState(), Schritt für Schritt.

  1. Achten Sie darauf, dass activityLayoutInfo einige DisplayFeature-Properties enthält und dass mindestens eine davon ein FoldingFeature ist. Andernfalls sollten Sie nichts unternehmen:

step1/FoldingStateActor.kt

val foldingFeature = activeWindowLayoutInfo?.displayFeatures
            ?.firstOrNull { it is FoldingFeature } as FoldingFeature?
            ?: return
  1. Berechnen Sie die Position der Faltung, um sicherzustellen, dass sich die Geräteposition auf Ihr Layout auswirkt und nicht außerhalb der Grenzen Ihrer Hierarchie liegt:

step1/FoldingStateActor.kt

val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
            foldingFeature,
            cameraViewfinder.parent as View
        ) ?: return

Sie haben nun festgestellt, dass Sie ein FoldingFeature haben, das sich auf Ihr Layout auswirkt. Sie müssen also Ihre Inhalte verschieben.

  1. Prüfen Sie, ob FoldingFeature HALF_OPEN ist. Andernfalls stellen Sie die Position Ihrer Inhalte wieder her. Wenn es HALF_OPEN ist, müssen Sie eine weitere Prüfung durchführen und je nach Ausrichtung der Faltung anders vorgehen:

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

Wenn der Falz VERTICAL ist, verschieben Sie die Inhalte nach rechts. Andernfalls verschieben Sie sie über die Falzposition.

  1. Erstelle und starte deine App. Klappe dann dein Gerät auf und stelle es auf den Tisch, um zu sehen, wie sich der Inhalt entsprechend anpasst.

7. Glückwunsch!

In diesem Codelab haben Sie einige Funktionen kennengelernt, die nur auf faltbaren Geräten verfügbar sind, z. B. der Rückdisplaymodus oder der Tischmodus. Außerdem haben Sie erfahren, wie Sie diese Funktionen mit Jetpack WindowManager nutzen können.

Sie sind bereit, eine großartige Nutzererfahrung für Ihre Kamera-App zu implementieren.

Weitere Informationen

Referenz