Obraz w obrazie (PIP) to specjalny rodzaj trybu wielu okien używany głównie do odtwarzania filmów. Umożliwia użytkownikowi oglądanie filmu w małym oknie przypiętym do rogu ekranu podczas nawigowania między aplikacjami lub przeglądania treści na ekranie głównym.
Funkcja PiP wykorzystuje interfejsy API wielu okien dostępne w Androidzie 7.0, aby wyświetlać przypięte okno nakładki wideo. Aby dodać obraz w obrazie w aplikacji, musisz zarejestrować swoją aktywność, w razie potrzeby przełączyć ją na tryb obrazu w obrazie i upewnić się, że elementy interfejsu są ukryte, a odtwarzanie filmu będzie kontynuowane, gdy aktywność jest w tym trybie.
Z tego przewodnika dowiesz się, jak dodać obraz w obrazie w sekcji Utwórz do aplikacji za pomocą implementacji wideo w funkcji Utwórz. Te sprawdzone metody w praktyce znajdziesz w aplikacji socialite.
Konfigurowanie funkcji PIP w aplikacji
W tagu aktywności w pliku AndroidManifest.xml
wykonaj te czynności:
- Dodaj
supportsPictureInPicture
i ustaw jego wartośćtrue
, aby zadeklarować, że będziesz używać PiP w aplikacji. Dodaj właściwość
configChanges
i ustaw ją jakoorientation|screenLayout|screenSize|smallestScreenSize
, aby określić, że Twoja aktywność ma obsługiwać zmiany konfiguracji układu. Dzięki temu aktywność nie zostanie ponownie uruchomiona, gdy zmieni się układ podczas przenoszenia trybu obrazu w obrazie.<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
. Aby uzyskać dostęp do aktywności, będziesz używać tego rozszerzenia wiele razy w całym przewodniku.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 funkcji PIP w aplikacji na Androida w wersji wcześniejszej niż 12
Aby dodać obraz w obrazie w przypadku systemu starszego niż Android 12, użyj narzędzia addOnUserLeaveHintProvider
. Aby dodać obraz w obrazie w wersji starszej na Androida 12, wykonaj te czynności:
- Dodaj bramę wersji, aby dostęp do tego kodu był możliwy tylko od wersji O do R.
- Użyj klucza
DisposableEffect
z kluczemContext
. - W elemencie
DisposableEffect
zdefiniuj działanie wywoływane w przypadku wywołania funkcjionUserLeaveHintProvider
za pomocą funkcji lambda. W funkcji lambda wywołaj metodęenterPictureInPictureMode()
w elemenciefindActivity()
i przekaż go w elemenciePictureInPictureParams.Builder().build()
. - Dodaj funkcję
addOnUserLeaveHintListener
, używając funkcjifindActivity()
, i przekaż parametr lambda. - W funkcji
onDispose
dodaj funkcjęremoveOnUserLeaveHintListener
, używając parametrufindActivity()
i przekaż go w elemencie 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_TAG, "API does not support PiP") }
Dodawanie obrazu w obrazie w aplikacji w przypadku rezygnacji z Androida 12
Po wprowadzeniu Androida 12 element PictureInPictureParams.Builder
jest dodawany przez modyfikator przekazywany do odtwarzacza wideo w aplikacji.
- Utwórz
modifier
i wywołaj na nimonGloballyPositioned
. Współrzędne układu przydadzą się w późniejszym kroku. - Utwórz zmienną dla kolumny
PictureInPictureParams.Builder()
. - Dodaj instrukcję
if
, aby sprawdzić, czy pakiet SDK ma wersję S lub wyższą. Jeśli tak, dodaj parametrsetAutoEnterEnabled
do kreatora i ustaw go natrue
, aby włączać tryb obrazu w obrazie po przesunięciu palcem. Zapewnia to płynność animacji niżenterPictureInPictureMode
. - Użyj aplikacji
findActivity()
, by zadzwonić pod numersetPictureInPictureParams()
. Zadzwoń dobuild()
nabuilder
i przekaż je.
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)
Dodawanie obrazu w obrazie za pomocą przycisku
Aby włączyć tryb obrazu w obrazie kliknięciem przycisku, wywołaj metodę enterPictureInPictureMode()
w findActivity()
.
Te parametry są już ustawiane przez poprzednie wywołania funkcji PictureInPictureParams.Builder
, więc nie musisz ustawiać nowych parametrów w konstruktorze. Jeśli jednak zechcesz zmienić
parametry kliknięcia przycisku, możesz je tutaj ustawić.
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ługa UI w trybie PIP
Gdy włączysz tryb obraz w obrazie, cały interfejs Twojej aplikacji pojawi się w tym oknie, chyba że określisz, jak ma wyglądać interfejs w tym trybie i jak z niego wyjść.
Po pierwsze musisz wiedzieć, czy aplikacja działa w trybie PIP. Aby to zrobić, użyj narzędzia OnPictureInPictureModeChangedProvider
.
Poniższy kod informuje, czy Twoja aplikacja działa w trybie obrazu w obrazie.
@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ą być wyświetlane, gdy aplikacja przechodzi w tryb obrazu w obrazie:
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 włącza tryb obraz w obrazie w odpowiednich momentach
Tryb obraz w obrazie aplikacji nie powinien włączać się w tych sytuacjach:
- Zatrzymanie lub wstrzymanie filmu.
- Jeśli jesteś na innej stronie aplikacji niż odtwarzacz wideo:
Aby kontrolować, kiedy aplikacja ma przechodzić w tryb obrazu w obrazie, dodaj zmienną śledzącą stan odtwarzacza za pomocą mutableStateOf
.
Przełącz stan w zależności od tego, czy film jest odtwarzany
Aby przełączać stan w zależności od tego, czy odtwarzacz jest odtwarzany, dodaj do niego detektor. Przełącz stan zmiennej stanu w zależności od tego, czy odtwarzacz odtwarza treści:
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
Przełącz stan w zależności od tego, czy odtwarzacz został zwolniony
Po zwolnieniu odtwarzacza ustaw zmienną stanową na false
:
fun releasePlayer() { shouldEnterPipMode = false }
Użyj stanu, aby określić, czy tryb obrazu w obrazie ma być włączony (starszy niż 12)
- Dodanie parametru PIP sprzed 12. roku używa parametru
DisposableEffect
, więc musisz utworzyć nową zmienną za pomocąrememberUpdatedState
, podającnewValue
jako zmienną stanu. Dzięki temu zaktualizowana wersja będzie używana w:DisposableEffect
. Do funkcji lambda, która określa działanie po wywołaniu wywołania
OnUserLeaveHintListener
, dodaj instrukcjęif
ze zmienną stanową otaczającą wywołanieenterPictureInPictureMode()
: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") }
Użyj stanu, aby określić, czy tryb obraz w obrazie ma być włączony (po Androidzie 12)
Przekaż zmienną stanową do obiektu setAutoEnterEnabled
, aby aplikacja wchodziła w tryb PiP tylko 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 setSourceRectHint
w celu zaimplementowania płynnej animacji
Interfejs API setSourceRectHint
tworzy płynniejszą animację przejścia w tryb obrazu w obrazie. W Androidzie 12 i nowszych tworzy ona również płynniejszą animację wychodzenia z trybu obrazu w obrazie.
Dodaj ten interfejs API do kreatora funkcji PIP, aby wskazać obszar aktywności, który będzie widoczny po przejściu do tej funkcji.
- Dodaj
setSourceRectHint()
do interfejsubuilder
tylko wtedy, gdy stan określa, że aplikacja powinna przechodzić w tryb obrazu w obrazie. Pozwala to uniknąć obliczaniasourceRect
, gdy aplikacja nie musi korzystać z trybu PIP. - Aby ustawić wartość
sourceRect
, użyjlayoutCoordinates
z funkcjionGloballyPositioned
na modyfikatorze. - Wywołaj
setSourceRectHint()
wbuilder
i przekaż zmiennąsourceRect
.
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ć format obrazu okna PIP
Aby ustawić współczynnik proporcji okna obrazu w obrazie, możesz wybrać konkretny współczynnik lub użyć szerokości i wysokości rozmiaru wideo w odtwarzaczu. Jeśli używasz odtwarzacza media3, przed ustawieniem współczynnika proporcji upewnij się, że odtwarzacz nie ma wartości null i że jego rozmiar nie jest równy VideoSize.UNKNOWN
.
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 współczynnik proporcji dla wysokości i szerokości odtwarzacza, korzystając ze składni dostosowanej do Twojego odtwarzacza. Pamiętaj, że jeśli odtwarzacz zmieni rozmiar w trakcie inicjowania, to jeśli nie mieści się on w dopuszczalnych granicach formatu obrazu, aplikacja ulegnie awarii. Może być konieczne dodanie kryteriów obliczania współczynnika proporcji, podobnie jak w przypadku odtwarzacza media3.
Dodaj działania zdalne
Jeśli chcesz dodać elementy sterujące (odtwarzanie, wstrzymywanie itp.) do okna obrazu w obrazie, utwórz RemoteAction
dla każdego elementu sterującego, który chcesz dodać.
- Dodaj stałe do sterowania 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
dla elementów sterujących w oknie obrazu w obrazie. - Następnie dodaj
BroadcastReceiver
i zastąponReceive()
, aby określić działania każdego przycisku. Aby zarejestrować odbiornik i działania zdalne, użyjDisposableEffect
. Po zutylizowaniu odtwarzacza wyrejestruj odbiornik.@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ż listę działań zdalnych do
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)
Dalsze kroki
W tym przewodniku poznaliśmy sprawdzone metody dodawania obrazu w obrazie do tworzenia wiadomości zarówno w wersji sprzed Androida 12, jak i nowszej.
- Zapoznaj się z aplikacją Społecznościową, aby poznać sprawdzone metody działania funkcji PiP w funkcji tworzenia wiadomości.
- Więcej informacji znajdziesz we wskazówkach dotyczących projektowania systemu PiP.