Jetpack-Bibliothek „Bild im Bild“ verwenden

Die Picture-in-Picture-Jetpack-Bibliothek (PiP) bietet Android-App-Entwicklern eine optimierte und robuste Lösung zur Implementierung der PiP-Funktionalität, insbesondere für Apps zur Medienwiedergabe, Videoanrufe und Navigation. Durch die Bereitstellung einer einheitlichen API trägt die Bibliothek dazu bei, Boilerplate-Code und häufige In-App-Fehler zu vermeiden und die Gesamtqualität der PiP-Nutzererfahrung zu verbessern.

Die PiP-Jetpack-Bibliothek ergänzt die vorhandenen PiP-APIs, indem sie mehrere wichtige Herausforderungen und Inkonsistenzen im Android-Ökosystem angeht:

  • OS-Fragmentierung: Die Bibliothek verarbeitet automatisch Unterschiede bei PiP-API Aufrufen in verschiedenen Android-Versionen, z. B. die Verwendung von enterPictureInPictureMode vor Android 12 und isAutoEnterEnabled danach. Entwickler müssen sich also nicht um Versions unterschiede kümmern.
  • Falsche PiP-Parameter: Sie bietet eine einheitliche Lösung zum korrekten Festlegen von PiP-Parametern, z. B. setSourceRectHint, um bei der Medienwiedergabe reibungslose und hochwertige Animationen zu erstellen.
  • Einheitliche PiP-Status-Callbacks: Sie fasst onPictureInPictureModeChanged und onPictureInPictureUiStateChanged in einer einzigen, einheitlichen Callback- Schnittstelle (PictureInPictureDelegate.OnPictureInPictureEventListener) zusammen, um den Status und die UI-Verwaltung zu vereinfachen.
  • Reduzierung von Boilerplate-Code: Die Bibliothek reduziert die Menge an sich wiederholendem Boilerplate-Code, indem sie vordefinierte Sätze von RemoteActions für häufige Anwendungs fälle wie Wiedergabesteuerungen und Videoanrufaktionen bietet.
  • Zukunftssicherheit: Weitere PiP-Funktionen werden über die Jetpack Bibliothek bereitgestellt, sodass Nutzer mit minimalem Aufwand auf zusätzliche Funktionen zugreifen können.

Migrationsworkflow

Kategorie des Anwendungsfalls der App und Legacy-PiP-Logik identifizieren:

Kategorien:Videowiedergabe, Navigation oder Videoanruf.

Zu identifizierende Legacy-PiP-Logik :

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. AndroidManifest-Konfiguration

Die Aktivität, die in den PiP-Modus wechselt, muss die Unterstützung in AndroidManifest.xml mit den erforderlichen configChanges deklarieren, um unnötige Neustarts zu vermeiden:

<activity
android:name="VideoActivity" android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>

3. Umgebung einrichten

Die erforderlichen Abhängigkeiten zu build.gradle hinzufügen:

dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }

Verwenden Sie die neuesten AndroidX-Bibliotheken für die Abhängigkeiten und sehen Sie auf der Seite mit den Releases nach.

4. Vorlage auswählen und initialisieren

Wählen Sie die Implementierungsvorlage aus, die am besten zum Anwendungsfall der App passt:

  • Navigation und Videoanruf: BasicPictureInPicture; nahtlose Größenänderung wird in der Regel nicht unterstützt und Sie benötigen keinen Hinweis auf das Quellrechteck.
  • Videowiedergabe: VideoPlaybackPictureInPicture; verfolgt automatisch die Grenzen der Playeransicht für den Hinweis auf das Quellrechteck und ermöglicht standardmäßig eine nahtlose Größenänderung.

Um die Jetpack-Bibliothek zu verwenden, ersetzen Sie Ihre vorhandene benutzerdefinierte PiP-Implementierung durch die Jetpack-Bibliotheks-APIs. Die Komplexität und die Kosten der Einführung variieren je nach aktueller Implementierung der App.

In den folgenden Abschnitten werden einige der typischen Anwendungsfälle von PiP und die erforderlichen Implementierungsschritte beschrieben:

Die App informiert die Bibliothek über den aktiven oder inaktiven Status der Navigation und legt das Seitenverhältnis fest. Die Jetpack-Bibliothek übernimmt den Rest.

Wichtige Unterschiede

  1. Es ist nicht erforderlich, zwischen dem automatischen Wechsel in den PiP-Modus und dem Legacy-Wechsel in den PiP-Modus auf App-Seite zu unterscheiden.
  2. Zusammengefasste Callback-Schnittstellen.
  3. Neuer PictureInPictureParams-Builder für die Abwärtskompatibilität.

Videoanruf

Die App informiert die Bibliothek über den aktiven oder inaktiven Status des Anrufs und legt das Seitenverhältnis fest.

Wichtige Unterschiede

  1. Es ist nicht erforderlich, zwischen dem automatischen Wechsel in den PiP-Modus und dem Legacy-Wechsel in den PiP-Modus auf App-Seite zu unterscheiden.
  2. Zusammengefasste Callback-Schnittstellen.
  3. Neuer PictureInPictureParams-Builder für die Abwärtskompatibilität.
  4. Standardisierte Aktionssymbole für Videoanrufe.

5. Code migrieren

  • Eintrittslogik: Ersetzen Sie API-spezifische Logik wie setAutoEnterEnabled für Android 12 und höher oder onUserLeaveHint für Android 11 und niedriger durch setEnabled. Lösen Sie dies aus, wenn sich der Status der Berechtigung für den PiP-Modus ändert.
  • Callbacks:Fassen Sie onPictureInPictureModeChanged (Layout-Umschaltung) und onPictureInPictureUiStateChanged (Animation/Status) in einem einheitlichen ereignisbasierten Callback onPictureInPictureEvent zusammen.
  • Aktionen und Parameter:Aktualisieren Sie die Parameter mit setActions und setAspectRatio in der Vorlageninstanz, wenn sie sich ändern.

Referenzimplementierungsmuster

Beispiele für Implementierungen.

Navigation und Videoanruf

class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: BasicPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = BasicPictureInPicture(this)
        // BasicPictureInPicture is ideal for Navigation and Video call use cases.
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ }
        }
    }
}

Videowiedergabe

class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = VideoPlaybackPictureInPicture(this)
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
            ContentScreen(pictureInPictureImpl)
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ }
            PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ }
            PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ }
        }
    }

    @Composable
    fun ContentScreen(pipController: VideoPlaybackPictureInPicture) {
        DisposableEffect(pipController) {
            onDispose {
                pipController.close()
            }
        }
    }
}