Tryb obrazu w obrazie (PIP) to specjalny rodzaj trybu wielu okien używany głównie do do odtwarzania filmów. Pozwala użytkownikowi obejrzeć film w małym oknie przypiętym w rogu ekranu podczas przechodzenia między aplikacjami lub przeglądania treści na ekranie głównym.
Obraz w obrazie wykorzystuje interfejsy API trybu wielu okien dostępne na Androidzie 7.0, przypiętego okna nakładki wideo. Aby dodać obraz w obrazie do aplikacji, musisz zarejestrować aktywności, w razie potrzeby przełączaj ją na tryb obrazu w obrazie i upewnij się, że elementy interfejsu są ukryte, a film jest odtwarzany w trybie PIP.
Z tego przewodnika dowiesz się, jak dodać PiP w Compose do swojej aplikacji za pomocą implementacji Compose Video. Zobacz aplikację Społecznościowe, aby zobaczyć najlepsze w praktyce.
Konfigurowanie aplikacji pod kątem obrazu w obrazie
W tagu aktywności w pliku AndroidManifest.xml
wykonaj te czynności:
- Dodaj
supportsPictureInPicture
i ustaw wartośćtrue
, aby zadeklarować, że będziesz za pomocą funkcji obraz w obrazie w aplikacji. Dodaj
configChanges
i ustaw tę wartość naorientation|screenLayout|screenSize|smallestScreenSize
, aby określić, że aktywność obsługuje zmiany konfiguracji układu. Dzięki temu Twoja aktywność nie jest uruchamiany ponownie po zmianie układu w trakcie przechodzenia w trybie PIP.<activity android:name=".SnippetsActivity" android:exported="true" android:supportsPictureInPicture="true" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" android:theme="@style/Theme.Snippets">
W kodzie tworzenia wykonaj te czynności:
- Dodaj to rozszerzenie na stronie
Context
. Użyjesz tego rozszerzenia wiele razy w trakcie całego przewodnika.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") }
Dodawanie obrazu w obrazie podczas opuszczania aplikacji na Androidzie 12
Aby dodać obraz w obrazie na urządzeniach z systemem starszym niż Android 12, użyj addOnUserLeaveHintProvider
. Obserwuj
Aby dodać obraz w obrazie na urządzeniach z systemem starszym niż Android 12:
- Dodaj bramkę wersji, aby dostęp do tego kodu był możliwy tylko w wersjach O aż do R.
- Użyj klucza
DisposableEffect
z kluczemContext
. - W
DisposableEffect
określ zachowanie, które ma być wykonywane, gdy funkcjaonUserLeaveHintProvider
zostanie wywołana za pomocą funkcji lambda. W funkcji lambda wywołaj funkcjęenterPictureInPictureMode()
w obiekciefindActivity()
i przekaż parametrPictureInPictureParams.Builder().build()
. - Dodaj
addOnUserLeaveHintListener
za pomocą funkcjifindActivity()
i przekazuj do niej wyrażenie lambda. - W usłudze
onDispose
dodaj użytkownikaremoveOnUserLeaveHintListener
za pomocą funkcjifindActivity()
i przepowiem lambda.
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 info", "API does not support PiP") }
Dodanie PiP w aplikacji podczas opuszczania aplikacji na Androidzie 12
Po Androidzie 12 sekcja PictureInPictureParams.Builder
jest dodawana za pomocą
który jest przekazywany do odtwarzacza wideo aplikacji.
- Utwórz
modifier
i wywołaj za jego pomocąonGloballyPositioned
. współrzędnych układu, które będą używane w dalszym kroku. - Utwórz zmienną dla:
PictureInPictureParams.Builder()
. - Dodaj instrukcję
if
, by sprawdzić, czy pakiet SDK ma wersję S lub wyższą. Jeśli tak, dodajsetAutoEnterEnabled
w kreatorze i ustaw natrue
, aby włączyć obraz w obrazie tryb po przesunięciu palcem. Zapewnia to płynniejszą animację niż przejście przez cały czas.enterPictureInPictureMode
- Użyj kodu
findActivity()
, aby zadzwonić pod numersetPictureInPictureParams()
. Zadzwoń pod numerbuild()
nabuilder
i przekaż informacje.
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)
Dodaj obraz w obrazie za pomocą przycisku
Aby przejść do trybu obrazu w obrazie, kliknij przycisk enterPictureInPictureMode()
na findActivity()
.
Te parametry są już ustawiane przez poprzednie wywołania funkcji
PictureInPictureParams.Builder
, więc nie musisz ustawiać nowych parametrów
w kreatorze. Jeśli jednak chcesz zmienić parametry przycisku,
kliknij, aby ustawić je tutaj.
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!") }
Obsługuj interfejs w trybie obrazu w pionie
Po włączeniu trybu obrazu w obrazie cały interfejs aplikacji pojawi się w oknie tej funkcji, chyba że określ, jak powinien wyglądać interfejs w trybie PIP.
Przede wszystkim musisz wiedzieć, kiedy aplikacja jest w trybie PIP. Za pomocą
OnPictureInPictureModeChangedProvider
.
Poniższy kod informuje, czy aplikacja działa w trybie PIP.
@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 } }
Za pomocą rememberIsInPipMode()
możesz teraz wybierać elementy interfejsu, które mają się wyświetlać
gdy aplikacja przejdzie w tryb PIP:
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() }
Upewnij się, że aplikacja przechodzi w tryb obrazu w obrazie we właściwym czasie
Aplikacja nie powinna przechodzić w tryb obrazu w pliku w tych sytuacjach:
- czy odtwarzanie zostało zatrzymane lub wstrzymane;
- Jeśli jesteś na innej stronie aplikacji niż odtwarzacz wideo:
Aby kontrolować, kiedy aplikacja przechodzi w tryb PiP, dodaj zmienną, która śledzi stan odtwarzacza za pomocą mutableStateOf
.
przełączać stan w zależności od tego, czy odtwarzany jest film;
Aby przełączać stan w zależności od tego, czy odtwarza się film, dodaj detektor w odtwarzaczu wideo. Przełącz stan zmiennej stanu w zależności od tego, czy gracz gra:
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
Przełącz stan w zależności od tego, czy gracz jest zwolniony
Po uwolnieniu odtwarzacza ustaw zmienną stanu na false
:
fun releasePlayer() { shouldEnterPipMode = false }
Używanie stanu do określania, czy ma być włączony tryb obrazu w obrazie (starsze wersje Androida 12)
- Dodawanie obrazu w obrazie sprzed 12 roku wymaga użycia
DisposableEffect
, więc musisz utworzyć nową zmienną według wymiarurememberUpdatedState
z wartościąnewValue
ustawioną jako „stan”. Dzięki temu w ramachDisposableEffect
zostanie użyta zaktualizowana wersja. W parametrze lambda określającym działanie funkcji
OnUserLeaveHintListener
dodaj instrukcjęif
ze zmienną stanu wokół wywołania funkcjienterPictureInPictureMode()
: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 info", "API does not support PiP") }
Użycie stanu do określenia, czy włączono tryb obrazu w obrazie (po Androidzie 12)
Przekazuj zmienną stanu do funkcji setAutoEnterEnabled
, tak aby aplikacja trafiała tylko do
Tryb obraz w obrazie w odpowiednim momencie:
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)
Użyj polecenia setSourceRectHint
, aby zaimplementować płynną animację
Interfejs API setSourceRectHint
tworzy płynniejszą animację podczas włączania trybu PiP. W Androidzie 12 i nowszych ta funkcja zapewnia też płynniejszą animację przy zamykaniu trybu obraz w obrazie.
Dodaj ten interfejs API do konstruktora obrazu w obrazie, aby wskazać obszar działania, który jest
widoczne po przejściu do obrazu w obrazie.
- Dodaj
setSourceRectHint()
dobuilder
tylko wtedy, gdy stan określa, że aplikacja powinna przejść do trybu PiP. Pozwala to uniknąć obliczania wartościsourceRect
, gdy aplikacja nie muszą wprowadzać funkcji PIP. - Aby ustawić wartość
sourceRect
, użyj podanego parametrulayoutCoordinates
z funkcjionGloballyPositioned
na modyfikatorze. - Zadzwoń do
setSourceRectHint()
nabuilder
i wjedź wsourceRect
.
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)
Użyj setAspectRatio
, aby ustawić współczynnik proporcji okna PIP
Aby ustawić format obrazu okna PiP, możesz wybrać konkretny format obrazu lub użyć szerokości i wysokości rozmiaru filmu odtwarzacza. Jeśli jesteś
używając odtwarzacza media3, sprawdź, czy odtwarzacz nie ma wartości null oraz
przed ustawieniem proporcji rozmiar filmu nie jest równy VideoSize.UNKNOWN
współczynnik proporcji.
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)
Jeśli korzystasz z odtwarzacza niestandardowego, ustaw format obrazu na jego wysokości i szerokości, użyj składni charakterystycznej dla Twojego odtwarzacza. Pamiętaj, że jeśli Twój odtwarzacz zmienia rozmiar podczas inicjowania, jeśli wykracza poza dozwolony zakres w jakim formacie obrazu, aplikacja ulega awarii. Konieczne może być dodanie weryfikacji gdy współczynnik proporcji można obliczać, podobnie jak w przypadku mediów3. .
Dodaj działania zdalne
Jeśli chcesz dodać elementy sterujące (odtwarzanie, wstrzymywanie itp.) do okna PIP, utwórz
RemoteAction
dla każdego ustawienia, które chcesz dodać.
- Dodaj stałe wartości dla elementów sterujących transmisją:
// 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
- Utwórz listę
RemoteActions
elementów sterujących w oknie PiP. - Następnie dodaj
BroadcastReceiver
i zastąponReceive()
, aby ustawić działania każdego z przycisków. Aby zarejestrować odbiornik i działania zdalne, użyjDisposableEffect
. Po utylizacji odtwarzacza wyrejestruj odbiorcy.@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) } } }
- Przekaż do
PictureInPictureParams.Builder
listę swoich czynności zdalnych: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)
Dalsze kroki
W tym przewodniku przedstawiliśmy sprawdzone metody dodawania obrazu w obrazie podczas tworzenia starszych niż Android 12 i nowszych wersji Androida 12.
- W aplikacji Społecznościowe znajdziesz sprawdzone metody Utwórz obraz w obrazie w działaniu.
- Więcej informacji znajdziesz we wskazówkach dotyczących projektowania PiP.