Korzystanie z funkcji obraz w obrazie (PIP)

Wypróbuj tworzenie
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak obsługiwać tryb obrazu w obrazie w Compose.

Od Androida 8.0 (poziom API 26) aktywności mogą być uruchamiane w trybie obraz w obrazie. PiP to specjalny rodzaj trybu wielu okien używany do odtwarzania filmów, rozmów wideo i nawigacji. Umożliwia użytkownikowi przypięcie okna bieżącej aktywności do rogu ekranu podczas przełączania aplikacji lub przeglądania treści na głównym ekranie.

Tryb obrazu w obrazie korzysta z interfejsów API obsługujących wiele okien, które są dostępne w Androidzie 7.0, aby wyświetlać przypięte okno nakładki wideo. Aby dodać do aplikacji tryb obrazu w obrazie, musisz zarejestrować działania, które go obsługują, w razie potrzeby przełączyć działanie w tryb obrazu w obrazie i upewnić się, że elementy interfejsu są ukryte, a odtwarzanie wideo jest kontynuowane, gdy działanie jest w trybie obrazu w obrazie.

Okno PiP pojawi się w najwyższej warstwie ekranu, w rogu wybranym przez system.

Tryb obrazu w obrazie jest też obsługiwany na zgodnych urządzeniach z Androidem TV OS z Androidem 14 (API na poziomie 34) lub nowszym. Chociaż istnieje wiele podobieństw, podczas korzystania z obrazu w obrazie na telewizorze należy wziąć pod uwagę dodatkowe kwestie.

Jak użytkownicy mogą wchodzić w interakcję z oknem PiP

Użytkownicy mogą przeciągać okno PIP w inne miejsce. Od Androida 12 użytkownicy mogą też:

  • Kliknij okno, aby wyświetlić przełącznik pełnego ekranu, przycisk zamykania, przycisk ustawień i działania niestandardowe udostępniane przez aplikację (np. elementy sterujące odtwarzaniem).

  • Kliknij dwukrotnie okno, aby przełączać się między bieżącym rozmiarem obrazu w obrazie a maksymalnym lub minimalnym rozmiarem obrazu w obrazie. Na przykład kliknięcie dwukrotne zmaksymalizowanego okna spowoduje jego zminimalizowanie i odwrotnie.

  • Ukryj okno, przeciągając je do lewej lub prawej krawędzi. Aby wycofać okno z ukrycia, kliknij widoczną część ukrytego okna lub przeciągnij ją.

  • Zmień rozmiar okna PIP, używając gestu uszczypnięcia.

Aplikacja decyduje, kiedy bieżąca aktywność ma przejść w tryb obrazu w obrazie. Oto kilka przykładów:

  • Aktywność może przejść w tryb obrazu w obrazie, gdy użytkownik naciśnie przycisk ekranu głównego lub przesunie palcem w górę do ekranu głównego. Dzięki temu Mapy Google mogą nadal wyświetlać wskazówki, gdy użytkownik wykonuje w tym samym czasie inne czynności.

  • Gdy użytkownik wróci z filmu, aby przeglądać inne treści, aplikacja może przenieść wideo do trybu obraz w obrazie.

  • Aplikacja może przełączyć film w tryb obrazu w obrazie, gdy użytkownik ogląda końcówkę odcinka. Na ekranie głównym wyświetlają się informacje promocyjne lub podsumowujące dotyczące następnego odcinka serialu.

  • Aplikacja może umożliwiać użytkownikom dodawanie do kolejki dodatkowych treści podczas oglądania filmu. Film będzie nadal odtwarzany w trybie obrazu w obrazie, a na ekranie głównym będzie wyświetlana aktywność wyboru treści.

Deklarowanie obsługi trybu obraz w obrazie

Domyślnie system nie obsługuje automatycznie trybu obrazu w obrazie w przypadku aplikacji. Jeśli chcesz obsługiwać tryb obrazu w obrazie w swojej aplikacji, zarejestruj aktywność związaną z filmem w manifeście, ustawiając android:supportsPictureInPicture na true. Określ też, że Twoja aktywność obsługuje zmiany konfiguracji układu, aby nie była ponownie uruchamiana, gdy podczas przejść do trybu obrazu w obrazie nastąpią zmiany układu.

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

Wdrażanie obrazu w obrazie za pomocą Jetpack

Użyj biblioteki Jetpack Picture-in-Picture, aby wdrożyć funkcję obrazu w obrazie. Ułatwia ona integrację i ogranicza typowe problemy w aplikacji. Przykład użycia znajdziesz w naszej przykładowej aplikacji na platformę. Jeśli jednak wolisz zaimplementować tryb obrazu w obrazie za pomocą interfejsów API platformy, zapoznaj się z tą dokumentacją.

Przełączanie aktywności na tryb obrazu w obrazie

Od Androida 12 możesz przełączyć aktywność w tryb obrazu w obrazie, ustawiając flagę setAutoEnterEnabled na true. Dzięki temu ustawieniu aktywność automatycznie przełącza się w tryb obrazu w obrazie w razie potrzeby bez konieczności jawnego wywoływania enterPictureInPictureMode()onUserLeaveHint. Dodatkową zaletą jest płynniejsze przechodzenie między stanami. Więcej informacji znajdziesz w artykule Płynniejsze przechodzenie do trybu PIP podczas nawigacji przy użyciu gestów.

Jeśli kierujesz aplikację na Androida 11 lub starszego, aktywność musi wywołać funkcję enterPictureInPictureMode(), aby przejść do trybu obrazu w obrazie. Na przykład poniższy kod przełącza aktywność w tryb obrazu w obrazie, gdy użytkownik kliknie w interfejsie aplikacji specjalny przycisk:

override fun onActionClicked(action: Action) {
    if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
        activity?.enterPictureInPictureMode()
        return
    }
}

Możesz uwzględnić logikę, która przełącza aktywność w tryb obrazu w obrazie zamiast przechodzenia w tło. Na przykład Mapy Google przełączają się w tryb obrazu w obrazie, gdy użytkownik naciśnie przycisk ekranu głównego lub ostatnio używanych aplikacji podczas nawigacji. Możesz przechwycić ten przypadek, zastępując funkcję onUserLeaveHint():

override fun onUserLeaveHint() {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode()
    }
}

Zalecane: zapewnij użytkownikom płynne przejście do trybu obrazu w obrazie

W Androidzie 12 wprowadzono znaczące zmiany wizualne w animowanych przejściach między oknami pełnoekranowymi a oknami w trybie obraz w obrazie. Zdecydowanie zalecamy wprowadzenie wszystkich odpowiednich zmian. Po ich wprowadzeniu zostaną one automatycznie dostosowane do dużych ekranów, takich jak urządzenia składane i tablety, bez konieczności wykonywania dodatkowych czynności.

Jeśli Twoja aplikacja nie zawiera odpowiednich aktualizacji, przejścia w trybie „obraz w obrazie” nadal będą działać, ale animacje będą mniej dopracowane. Na przykład przejście z trybu pełnoekranowego do trybu obrazu w obrazie może spowodować zniknięcie okna PIP podczas przejścia, zanim pojawi się ponownie po jego zakończeniu.

Zmiany te obejmują:

  • Płynniejsze przechodzenie do trybu obrazu w obrazie podczas korzystania z nawigacji przy użyciu gestów
  • Ustawienie odpowiedniego sourceRectHint do włączania i wyłączania trybu obrazu w obrazie
  • Wyłączanie płynnej zmiany rozmiaru w przypadku treści innych niż filmy

Aby uzyskać informacje o tym, jak zapewnić płynne przejście, zapoznaj się z przykładową aplikacją PictureInPicture w Kotlinie na Androida.

Płynniejsze przechodzenie do trybu obrazu w obrazie podczas nawigacji przy użyciu gestów

Od Androida 12 flaga setAutoEnterEnabled zapewnia znacznie płynniejszą animację podczas przechodzenia do treści wideo w trybie obrazu w obrazie za pomocą nawigacji gestami, np. podczas przesuwania palcem w górę do ekranu głównego z trybu pełnoekranowego.

Aby wprowadzić tę zmianę, wykonaj te czynności:

  1. Użyj setAutoEnterEnabled, aby utworzyć PictureInPictureParams.Builder:
setPictureInPictureParams(PictureInPictureParams.Builder()
  .setAspectRatio(aspectRatio)
  .setSourceRectHint(sourceRectHint)
  .setAutoEnterEnabled(true)
  .build())
Note: When `setAutoEnterEnabled` is enabled, you don't need to explicitly
call [`enterPictureInPictureMode`][5] in [`onUserLeaveHint`][6].
  1. Zadzwoń pod numer setPictureInPictureParams, korzystając z aktualnych informacji PictureInPictureParams, z wyprzedzeniem. Aplikacja nie czeka na wywołanie zwrotne onUserLeaveHint (jak w Androidzie 11).

    Możesz na przykład wywołać funkcję setPictureInPictureParams przy pierwszym odtworzeniu i przy każdym kolejnym, jeśli zmieni się format obrazu.

  2. Wywołuj funkcję setAutoEnterEnabled(false) tylko wtedy, gdy jest to konieczne. Na przykład nie chcesz włączać trybu obrazu w obrazie, jeśli odtwarzanie jest wstrzymane.

Ustaw odpowiedni sourceRectHint do włączania i wyłączania trybu obrazu w obrazie

Od wprowadzenia funkcji Obraz w obrazie w Androidzie 8.0 parametr setSourceRectHint wskazywał obszar aktywności widoczny po przejściu do trybu obrazu w obrazie, np. granice widoku filmu w odtwarzaczu wideo.

W Androidzie 12 system używa sourceRectHint, aby zapewnić znacznie płynniejszą animację zarówno podczas włączania, jak i wyłączania trybu obraz w obrazie.

Aby prawidłowo ustawić sourceRectHint do włączania i wyłączania trybu obrazu w obrazie:

  1. Skonstruuj PictureInPictureParams, używając odpowiednich granic jako sourceRectHint. Zalecamy też dołączenie do odtwarzacza wideo odbiornika zmiany układu:
val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, left: Int,
            top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop:
            Int, oldRight: Int, oldBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }

mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
  1. W razie potrzeby zaktualizuj sourceRectHint, zanim system rozpocznie przejście do stanu wyjścia. Gdy system ma wyjść z trybu obrazu w obrazie, hierarchia widoków aktywności jest układana w konfiguracji docelowej (np. na pełnym ekranie). Aplikacja może dołączyć detektor zmiany układu do widoku głównego lub widoku docelowego (np. widoku odtwarzacza wideo), aby wykryć zdarzenie i zaktualizować wartość sourceRectHint przed rozpoczęciem animacji.
// Listener is called immediately after the user exits PiP but before animating.
playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
                    oldLeft, oldTop, oldRight, oldBottom ->
    if (left != oldLeft
        || right != oldRight
        || top != oldTop
        || bottom != oldBottom) {
        // The playerView's bounds changed, update the source hint rect to
        // reflect its new bounds.
        val sourceRectHint = Rect()
        playerView.getGlobalVisibleRect(sourceRectHint)
        setPictureInPictureParams(
            PictureInPictureParams.Builder()
                .setSourceRectHint(sourceRectHint)
                .build()
        )
    }
}
korzystając z widoku przekazanego do tej metody.

Wyłączanie płynnej zmiany rozmiaru w przypadku treści innych niż filmy

Android 12 dodaje flagę setSeamlessResizeEnabled, która zapewnia znacznie płynniejszą animację przenikania podczas zmiany rozmiaru treści innych niż wideo w oknie obrazu w obrazie. Wcześniej zmiana rozmiaru treści innych niż wideo w oknie PIP mogła powodować nieprzyjemne artefakty wizualne.

Aby włączyć płynną zmianę rozmiaru treści wideo:

setPictureInPictureParams(PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(true)
    .build())

Obsługa interfejsu podczas korzystania z trybu obrazu w obrazie

Gdy aktywność wchodzi w tryb obrazu w obrazie (PiP) lub z niego wychodzi, system wywołuje funkcję Activity.onPictureInPictureModeChanged() lub Fragment.onPictureInPictureModeChanged().

Android 15 wprowadza zmiany, które zapewniają jeszcze płynniejsze przejście do trybu obrazu w obrazie. Jest to korzystne w przypadku aplikacji, które mają elementy interfejsu nakładane na główny interfejs, który przechodzi w tryb obrazu w obrazie.

Deweloperzy używają wywołania zwrotnego onPictureInPictureModeChanged(), aby zdefiniować logikę, która przełącza widoczność nakładanych elementów interfejsu. To wywołanie zwrotne jest aktywowane po zakończeniu animacji wejścia lub wyjścia z trybu obrazu w obrazie. Od Androida 15 klasa PictureInPictureUiState zawiera nowy stan.

W tym nowym stanie interfejsu aplikacje kierowane na Androida 15 obserwują wywołanie wywołania zwrotnego Activity#onPictureInPictureUiStateChanged() z wartością isTransitioningToPip() natychmiast po rozpoczęciu animacji obrazu w obrazie. W trybie obrazu w obrazie wiele elementów interfejsu nie jest istotnych dla aplikacji. Na przykład widoki lub układy zawierające informacje takie jak sugestie, nadchodzące filmy, oceny i tytuły. Gdy aplikacja przejdzie w tryb obrazu w obrazie, użyj wywołania zwrotnego onPictureInPictureUiStateChanged(), aby ukryć te elementy interfejsu. Gdy aplikacja przechodzi do trybu pełnoekranowego z okna obrazu w obrazie, użyj wywołania zwrotnego onPictureInPictureModeChanged(), aby odkryć te elementy, jak pokazano w tych przykładach:

override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
    if (pipState.isTransitioningToPip()) {
        // Hide UI elements.
    }
}
override fun onPictureInPictureModeChanged(
    isInPictureInPictureMode: Boolean,
    newConfig: Configuration
) {
    super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
    if (!isInPictureInPictureMode) {
        // Unhide UI elements.
    }
}

Ten przełącznik szybkiej widoczności nieistotnych elementów interfejsu (w przypadku okna obrazu w obrazie) pomaga zapewnić płynniejszą animację wejścia w tryb obrazu w obrazie bez migotania.

Zastąp te wywołania zwrotne, aby ponownie narysować elementy interfejsu aktywności. Pamiętaj, że w trybie obrazu w obrazie Twoja aktywność jest wyświetlana w małym oknie. Użytkownicy nie mogą wchodzić w interakcję z elementami interfejsu aplikacji, gdy jest ona w trybie obrazu w obrazie, a szczegóły małych elementów interfejsu mogą być trudne do zobaczenia. Odtwarzanie filmów z minimalnym interfejsem zapewnia użytkownikom największą wygodę.

Jeśli Twoja aplikacja musi udostępniać niestandardowe działania w trybie obraz w obrazie, zapoznaj się z sekcją Dodawanie elementów sterujących na tej stronie. Usuń inne elementy interfejsu przed przejściem działania do trybu obrazu w obrazie i przywróć je, gdy działanie ponownie zajmie cały ekran.

Dodaj elementy sterujące

Gdy użytkownik otworzy menu okna (dotykając okna na urządzeniu mobilnym lub wybierając menu na pilocie telewizora), mogą się w nim pojawić elementy sterujące.

Jeśli aplikacja ma aktywną sesję multimedialną, pojawią się przyciski odtwarzania, wstrzymywania, następny i poprzedni.

Możesz też określić działania niestandardowe, tworząc PictureInPictureParams za pomocą PictureInPictureParams.Builder.setActions() przed wejściem w tryb obrazu w obrazie i przekazując parametry podczas włączania tego trybu za pomocą enterPictureInPictureMode(android.app.PictureInPictureParams) lub setPictureInPictureParams(android.app.PictureInPictureParams). Zachowaj ostrożność. Jeśli spróbujesz dodać więcej niż getMaxNumPictureInPictureActions(), otrzymasz tylko maksymalną liczbę.

Kontynuowanie odtwarzania filmu w trybie obraz w obrazie

Gdy aktywność przejdzie do trybu obrazu w obrazie, system umieści ją w stanie wstrzymania i wywoła metodę onPause(). Odtwarzanie filmu nie powinno być wstrzymywane, ale kontynuowane, jeśli aktywność zostanie wstrzymana podczas przechodzenia do trybu obraz w obrazie.

W Androidzie 7.0 (poziom interfejsu API 24) i nowszych należy wstrzymywać i wznawiać odtwarzanie wideo, gdy system wywołuje metody aktywności onStop()onStart(). Dzięki temu nie musisz sprawdzać, czy aplikacja jest w trybie obrazu w obrazie w onPause(), i wyraźnie kontynuować odtwarzania.

Jeśli nie ustawisz flagi setAutoEnterEnabled na true i musisz wstrzymać odtwarzanie w implementacji onPause(), sprawdź tryb obrazu w obrazie, wywołując isInPictureInPictureMode(), i odpowiednio obsłuż odtwarzanie. Przykład:

override fun onPause() {
    super.onPause()
    // If called while in PiP mode, don't pause playback.
    if (isInPictureInPictureMode) {
        // Continue playback.
    } else {
        // Use existing playback logic for paused activity behavior.
    }
}

Gdy aktywność przełączy się z trybu obrazu w obrazie z powrotem na tryb pełnoekranowy, system wznowi aktywność i wywoła metodę onResume().

Używanie jednej aktywności odtwarzania w trybie obraz w obrazie

W aplikacji użytkownik może wybrać nowy film podczas przeglądania treści na ekranie głównym, gdy aktywność odtwarzania filmu jest w trybie obrazu w obrazie. Odtwarzaj nowy film w ramach istniejącej aktywności odtwarzania w trybie pełnoekranowym, zamiast uruchamiać nową aktywność, która może zmylić użytkownika.

Aby mieć pewność, że do żądań odtwarzania wideo jest używana jedna aktywność, która w razie potrzeby przełącza się w tryb obrazu w obrazie i z niego wychodzi, ustaw w pliku manifestu atrybut android:launchMode aktywności na singleTask:

<activity android:name="VideoActivity"
    ...
    android:supportsPictureInPicture="true"
    android:launchMode="singleTask"
    ...

W aktywności zastąp onNewIntent() i obsłuż nowy film, w razie potrzeby zatrzymując odtwarzanie bieżącego filmu.

Obsługa trybu obrazu w obrazie w aplikacjach aparatu

Aby włączyć tryb obrazu w obrazie w aplikacjach aparatu, upewnij się, że aparat pozostaje aktywny w trybie obrazu w obrazie, nie zamykając go po wywołaniu funkcji onPause():

override fun onPause() {
    super.onPause()
    // Don't close the camera if the app is entering PiP mode
    if (!isInPictureInPictureMode) {
        closeCamera()
    }
}

Podobnie jak w przypadku innych zastosowań ukryj nieistotne elementy interfejsu (np. elementy sterujące i nakładki) oraz dodaj niestandardowe działania, aby sterować kamerą (np. zatrzymać nagrywanie lub przełączyć kamerę).

Obliczanie wartości sourceRectHint w celu zapewnienia płynnych przejść

Podanie dokładnego sourceRectHint z precyzyjnymi współrzędnymi ekranu wizjera aparatu jest niezbędne do płynnego wyświetlania animacji wejścia. Granice widoku podglądu możesz uzyskać za pomocą getGlobalVisibleRect() w ten sposób:

val previewView = findViewById<View>(R.id.preview_view)
val globalRect = Rect()
// Ensure the view is laid out before calling getGlobalVisibleRect() to get valid screen coordinates.
previewView.getGlobalVisibleRect(globalRect)
val params = PictureInPictureParams.Builder()
    .setSourceRectHint(globalRect)
    .build()
setPictureInPictureParams(params)

Uwagi dodatkowe

Funkcja obrazu w obrazie może być wyłączona na urządzeniach z niewielką ilością pamięci RAM. Zanim aplikacja zacznie korzystać z trybu obrazu w obrazie, sprawdź, czy jest on dostępny, wywołując funkcję hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

Tryb obrazu w obrazie jest przeznaczony do działań, które odtwarzają wideo pełnoekranowe. Podczas przełączania aktywności w tryb obrazu w obrazie unikaj wyświetlania czegokolwiek poza treściami wideo. Śledź, kiedy aktywność przechodzi w tryb obrazu w obrazie, i ukrywaj elementy interfejsu zgodnie z opisem w sekcji Obsługa interfejsu podczas korzystania z trybu obrazu w obrazie.

Gdy aktywność jest w trybie obrazu w obrazie, domyślnie nie otrzymuje fokusu wejściowego. Aby otrzymywać zdarzenia wejściowe w trybie obrazu w obrazie, użyj MediaSession.setCallback(). Więcej informacji o używaniu setCallback() znajdziesz w artykule Wyświetlanie karty Co jest grane.

Gdy aplikacja jest w trybie obrazu w obrazie, odtwarzanie wideo w oknie PIP może powodować zakłócenia dźwięku w innej aplikacji, np. w odtwarzaczu muzyki lub aplikacji do wyszukiwania głosowego. Aby tego uniknąć, poproś o aktywność audio, gdy zaczniesz odtwarzać film, i obsługuj powiadomienia o zmianie aktywności audio zgodnie z opisem w artykule Zarządzanie aktywnością audio. Jeśli w trybie PIP otrzymasz powiadomienie o utracie aktywności audio, wstrzymaj lub zatrzymaj odtwarzanie filmu.

Gdy aplikacja ma przejść do trybu obrazu w obrazie, pamiętaj, że tylko aktywność na górze przechodzi do tego trybu. W niektórych sytuacjach, np. na urządzeniach z wieloma oknami, może się zdarzyć, że podstawowa aktywność zostanie ponownie wyświetlona i będzie widoczna obok aktywności w trybie obrazu w obrazie. W takim przypadku należy podjąć odpowiednie działania, w tym wywołać funkcję zwrotną onResume() lub onPause() dla powiązanej aktywności. Możliwe jest też, że użytkownik wejdzie w interakcję z aktywnością. Jeśli na przykład wyświetlasz aktywność listy filmów i aktywność odtwarzanego filmu w trybie obraz w obrazie, użytkownik może wybrać nowy film z listy, a aktywność w trybie obraz w obrazie powinna się odpowiednio zaktualizować.

Dodatkowy przykładowy kod

Aby pobrać przykładową aplikację napisaną w języku Kotlin, zobacz Android PictureInPicture Sample (Kotlin).