Bild im Bild (BiB) ist eine spezielle Art des Mehrfenstermodus, der hauptsächlich für die Videowiedergabe verwendet wird. Nutzer können sich ein Video in einem kleinen Fenster ansehen, das in einer Ecke des Bildschirms angepinnt ist, während sie zwischen Apps wechseln oder Inhalte auf dem Hauptbildschirm durchsuchen.
BiB nutzt die in Android 7.0 verfügbaren Mehrfenster-APIs, um das angepinnte Video-Overlay-Fenster bereitzustellen. Wenn du BiB in deine App aufnehmen möchtest, musst du deine Aktivität registrieren, in den BiB-Modus wechseln und darauf achten, dass UI-Elemente ausgeblendet sind und die Videowiedergabe fortgesetzt wird, wenn sich die Aktivität im BiB-Modus befindet.
In diesem Leitfaden wird beschrieben, wie Sie BiB in Compose mithilfe einer Compose-Videoimplementierung in Ihre Anwendung einfügen. In der Socialite App können Sie diese Best Practices in der Praxis sehen.
App für BiB einrichten
Führen Sie im Aktivitäts-Tag der Datei AndroidManifest.xml
die folgenden Schritte aus:
- Füge
supportsPictureInPicture
hinzu und setze es auftrue
, um zu deklarieren, dass du BiB in deiner App verwendest. Fügen Sie
configChanges
hinzu und setzen Sie es auforientation|screenLayout|screenSize|smallestScreenSize
, um anzugeben, dass Ihre Aktivität Änderungen der Layoutkonfiguration verarbeitet. So wird deine Aktivität nicht neu gestartet, wenn Layoutänderungen während BiB-Modusübergängen auftreten.<activity android:name=".SnippetsActivity" android:exported="true" android:supportsPictureInPicture="true" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" android:theme="@style/Theme.Snippets">
Führen Sie im Code die folgenden Schritte aus:
- Diese Erweiterung auf
Context
hinzufügen. Sie verwenden diese Erweiterung in diesem Leitfaden mehrmals, um auf die Aktivität zuzugreifen.internal fun Context.findActivity(): ComponentActivity { var context = this while (context is ContextWrapper) { if (context is ComponentActivity) return context context = context.baseContext } throw IllegalStateException("Picture in picture should be called in the context of an Activity") }
BiB beim Verlassen der App für vor Android 12 hinzufügen
Wenn du BiB für Geräte vor Android 12 hinzufügen möchtest, verwende addOnUserLeaveHintProvider
. So fügst du BiB für ältere Versionen als Android 12 hinzu:
- Fügen Sie ein Versions-Gatter hinzu, sodass nur in den Versionen O bis R auf diesen Code zugegriffen wird.
- Verwende
DisposableEffect
mitContext
als Schlüssel. - Definieren Sie im
DisposableEffect
das Verhalten, wennonUserLeaveHintProvider
mit einer Lambda-Funktion ausgelöst wird. Rufen Sie in der Lambda-FunktionenterPictureInPictureMode()
auffindActivity()
auf und übergeben SiePictureInPictureParams.Builder().build()
. - Füge
addOnUserLeaveHintListener
mithilfe vonfindActivity()
hinzu und übergib die Lambda-Funktion. - Füge in
onDispose
removeOnUserLeaveHintListener
mitfindActivity()
hinzu und übergib die Lambda-Funktion.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i(PIP_TAG, "API does not support PiP") }
BiB beim Verlassen der App nach Android 12 hinzufügen
Nach Android 12 wird PictureInPictureParams.Builder
über einen Modifizierer hinzugefügt, der an den Videoplayer der App übergeben wird.
- Erstellen Sie eine
modifier
und rufen SieonGloballyPositioned
darauf auf. Die Layoutkoordinaten werden in einem späteren Schritt verwendet. - Erstellen Sie eine Variable für
PictureInPictureParams.Builder()
. - Fügen Sie eine
if
-Anweisung hinzu, um zu prüfen, ob das SDK S oder höher ist. Wenn ja, füge dem BuildersetAutoEnterEnabled
hinzu und setze ihn auftrue
, um den BiB-Modus beim Wischen aufzurufen. Die Animation ist so flüssiger als mitenterPictureInPictureMode
. - Verwenden Sie
findActivity()
, umsetPictureInPictureParams()
aufzurufen. Rufen Siebuild()
imbuilder
auf und übergeben Sie es.
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(true) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
BiB über eine Schaltfläche hinzufügen
Wenn du den BiB-Modus durch Klicken auf eine Schaltfläche aktivieren möchtest, ruf auf findActivity()
enterPictureInPictureMode()
auf.
Die Parameter wurden bereits durch vorherige Aufrufe von PictureInPictureParams.Builder
festgelegt, sodass Sie keine neuen Parameter für den Builder festlegen müssen. Wenn Sie jedoch Parameter beim Klicken ändern möchten, können Sie sie hier festlegen.
val context = LocalContext.current Button(onClick = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.findActivity().enterPictureInPictureMode( PictureInPictureParams.Builder().build() ) } else { Log.i(PIP_TAG, "API does not support PiP") } }) { Text(text = "Enter PiP mode!") }
Die Benutzeroberfläche im BiB-Modus verwenden
Wenn Sie in den BiB-Modus wechseln, erscheint die gesamte Benutzeroberfläche Ihrer Anwendung in das BiB-Fenster, es sei denn, Sie geben an, wie die Benutzeroberfläche im BiB-Modus ein- und ausgeblendet werden soll.
Als Erstes musst du wissen, wann sich deine App im BiB-Modus befindet. Dazu können Sie OnPictureInPictureModeChangedProvider
verwenden.
Der folgende Code teilt dir mit, ob sich deine App im BiB-Modus befindet.
@Composable fun rememberIsInPipMode(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val activity = LocalContext.current.findActivity() var pipMode by remember { mutableStateOf(activity.isInPictureInPictureMode) } DisposableEffect(activity) { val observer = Consumer<PictureInPictureModeChangedInfo> { info -> pipMode = info.isInPictureInPictureMode } activity.addOnPictureInPictureModeChangedListener( observer ) onDispose { activity.removeOnPictureInPictureModeChangedListener(observer) } } return pipMode } else { return false } }
Jetzt können Sie mit rememberIsInPipMode()
festlegen, welche UI-Elemente angezeigt werden sollen, wenn die App in den BiB-Modus wechselt:
val inPipMode = rememberIsInPipMode() Column(modifier = modifier) { // This text will only show up when the app is not in PiP mode if (!inPipMode) { Text( text = "Picture in Picture", ) } VideoPlayer() }
Achte darauf, dass deine App zur richtigen Zeit in den BiB-Modus wechselt
In den folgenden Situationen sollte der BiB-Modus in deiner App nicht aktiviert werden:
- Gibt an, ob das Video angehalten oder pausiert wird.
- Wenn du dich auf einer anderen Seite der App als der Videoplayer befindest:
Wenn du steuern möchtest, wann in deiner App der BiB-Modus aktiviert wird, füge eine Variable hinzu, die den Status des Videoplayers mithilfe eines mutableStateOf
verfolgt.
Status je nach Wiedergabestatus des Videos wechseln
Wenn Sie den Status je nachdem, ob der Videoplayer wiedergegeben wird, ändern möchten, fügen Sie dem Videoplayer einen Listener hinzu. Schalten Sie den Status der Statusvariablen danach um, ob der Player eine Wiedergabe ausführt oder nicht:
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
Status je nach Veröffentlichung des Players wechseln
Setze die Statusvariable nach der Veröffentlichung des Players auf false
:
fun releasePlayer() { shouldEnterPipMode = false }
Mit Status festlegen, ob der BiB-Modus aktiviert wird (vor Android 12)
- Da beim Hinzufügen von BiB vor 12 ein
DisposableEffect
verwendet wird, müssen Sie mitrememberUpdatedState
eine neue Variable erstellen, wobeinewValue
als Statusvariable festgelegt ist. Dadurch wird die aktualisierte Version inDisposableEffect
verwendet. Fügen Sie in der Lambda-Funktion, die das Verhalten beim Auslösen von
OnUserLeaveHintListener
definiert, eineif
-Anweisung mit der Statusvariable um den Aufruf vonenterPictureInPictureMode()
hinzu:val currentShouldEnterPipMode by rememberUpdatedState(newValue = shouldEnterPipMode) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { if (currentShouldEnterPipMode) { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i(PIP_TAG, "API does not support PiP") }
Status verwenden, um festzulegen, ob der BiB-Modus aktiviert wird (nach Android 12)
Übergeben Sie die Statusvariable an setAutoEnterEnabled
, damit Ihre Anwendung nur zur richtigen Zeit in den BiP-Modus wechselt:
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() // Add autoEnterEnabled for versions S and up if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
Mit setSourceRectHint
kannst du eine flüssige Animation implementieren
Die setSourceRectHint
API sorgt für eine flüssigere Animation für den Wechsel in den BiB-Modus. In Android 12 und höher ist die Animation beim Beenden des BiB-Modus flüssiger.
Füge diese API dem BiB-Builder hinzu, um den Bereich der Aktivität anzugeben, der nach der Umstellung auf BiB sichtbar ist.
- Füge dem
builder
nur dannsetSourceRectHint()
hinzu, wenn der Status definiert, dass die App in den BiB-Modus wechseln soll. Dadurch wird die Berechnung vonsourceRect
vermieden, wenn die App nicht BiB eingeben muss. - Zum Festlegen des Werts
sourceRect
verwenden Sie dielayoutCoordinates
aus der FunktiononGloballyPositioned
für den Modifikator. - Rufen Sie
setSourceRectHint()
fürbuilder
auf und übergeben Sie die VariablesourceRect
.
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
Mit setAspectRatio
das Seitenverhältnis des BiB-Fensters festlegen
Um das Seitenverhältnis des BiB-Fensters einzustellen, kannst du entweder ein bestimmtes Seitenverhältnis auswählen oder die Breite und Höhe der Videogröße des Players verwenden. Wenn Sie einen media3-Player verwenden, achten Sie vor dem Festlegen des Seitenverhältnisses darauf, dass der Player nicht null ist und die Videogröße des Players nicht VideoSize.UNKNOWN
entspricht.
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
Wenn du einen benutzerdefinierten Player verwendest, lege das Seitenverhältnis auf Höhe und Breite des Players fest. Verwende dabei die für deinen Player spezifische Syntax. Wenn sich die Größe des Players während der Initialisierung ändert und die Größe des Players außerhalb des zulässigen Seitenverhältnisses liegt, stürzt Ihre App ab. Möglicherweise müssen Sie prüfen, wann das Seitenverhältnis berechnet werden kann, ähnlich wie bei einem media3-Player.
Remote-Aktionen hinzufügen
Wenn Sie dem BiB-Fenster Steuerelemente (Wiedergabe, Pause usw.) hinzufügen möchten, müssen Sie für jedes gewünschte Steuerelement ein RemoteAction
erstellen.
- Fügen Sie Konstanten für die Übertragungssteuerung hinzu:
// Constant for broadcast receiver const val ACTION_BROADCAST_CONTROL = "broadcast_control" // Intent extras for broadcast controls from Picture-in-Picture mode. const val EXTRA_CONTROL_TYPE = "control_type" const val EXTRA_CONTROL_PLAY = 1 const val EXTRA_CONTROL_PAUSE = 2
- Erstellen Sie eine Liste mit
RemoteActions
für die Steuerelemente im BiB-Fenster. - Als Nächstes fügen Sie ein
BroadcastReceiver
hinzu und überschreibenonReceive()
, um die Aktionen der einzelnen Schaltflächen festzulegen. Verwenden Sie einenDisposableEffect
, um den Empfänger und die Remote-Aktionen zu registrieren. Heben Sie dann die Registrierung des Empfängers auf.@RequiresApi(Build.VERSION_CODES.O) @Composable fun PlayerBroadcastReceiver(player: Player?) { val isInPipMode = rememberIsInPipMode() if (!isInPipMode || player == null) { // Broadcast receiver is only used if app is in PiP mode and player is non null return } val context = LocalContext.current DisposableEffect(player) { val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if ((intent == null) || (intent.action != ACTION_BROADCAST_CONTROL)) { return } when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) { EXTRA_CONTROL_PAUSE -> player.pause() EXTRA_CONTROL_PLAY -> player.play() } } } ContextCompat.registerReceiver( context, broadcastReceiver, IntentFilter(ACTION_BROADCAST_CONTROL), ContextCompat.RECEIVER_NOT_EXPORTED ) onDispose { context.unregisterReceiver(broadcastReceiver) } } }
- Übergeben Sie eine Liste Ihrer Remote-Aktionen an den
PictureInPictureParams.Builder
:val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() builder.setActions( listOfRemoteActions() ) if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(modifier = pipModifier)
Nächste Schritte
In diesem Leitfaden haben Sie Best Practices für das Hinzufügen von BiB in Compose in den Versionen vor und nach Android 12 kennengelernt.
- In der App Socialite finden Sie die Best Practices zum Erstellen von BiB in der Praxis.
- Weitere Informationen finden Sie in den PiP-Designanleitungen.