Dodawanie filmów przy użyciu obrazu w obrazie (PIP)

Od Androida 8.0 (poziom interfejsu API 26) Android umożliwia uruchamianie działań w trybie obraz w obrazie (PIP). Obraz w obrazie to specjalny rodzaj trybu wielu okien, używany głównie do odtwarzania filmów. Pozwala użytkownikowi oglądać film 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ść, która obsługuje tę funkcję, w razie potrzeby przełączyć ją na tryb tego obrazu oraz upewnić się, że elementy interfejsu są ukryte, a odtwarzanie filmu będzie kontynuowane, gdy aktywność jest w tym trybie.

Okno PIP pojawi się w górnej warstwie ekranu, w narożniku wybranym przez system.

Jak użytkownicy mogą korzystać z okna PIP

Użytkownicy mogą przeciągnąć okno funkcji obraz w obrazie w inne miejsce. Począwszy od Androida 12, użytkownicy mogą też:

  • Kliknij raz okno, aby wyświetlić przełącznik trybu pełnoekranowego, przycisk zamykania, ustawienia i funkcje 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 jego maksymalnym lub minimalnym. Na przykład dwukrotne dotknięcie okna zmaksymalizowanego powoduje jego zminimalizowanie, a odwrotność jest spójna.

  • Uchwyć okno, przeciągając je do lewej lub prawej krawędzi. Aby cofnąć okno, kliknij jego widoczną część lub przeciągnij ją poza nią.

  • Zmień rozmiar okna obrazu w obrazie za pomocą ściągania palcami.

Twoja aplikacja decyduje, kiedy bieżąca aktywność przejdzie w tryb obraz w obrazie. Oto kilka przykładów:

  • Aktywność może przechodzić w tryb PIP, gdy użytkownik kliknie przycisk ekranu głównego lub przesunie palcem w górę do góry. W ten sposób Mapy Google nadal będą wyświetlać wskazówki dojazdu, gdy użytkownik wykona w tym samym czasie inną aktywność.

  • Aplikacja może przenieść film do trybu obraz w obrazie, gdy użytkownik cofnie się, aby przeglądać inne treści.

  • Aplikacja może przełączać film w tryb PIP, gdy użytkownik ogląda koniec danego odcinka. Ekran główny zawiera informacje promocyjne lub podsumowanie następnego odcinka serialu.

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

Deklarowanie obsługi PiP

Domyślnie system nie obsługuje automatycznie funkcji PIP w aplikacjach. Jeśli chcesz, aby Twoja aplikacja obsługiwała funkcję PIP, zarejestruj aktywność wideo w pliku manifestu, ustawiając dla opcji android:supportsPictureInPicture wartość true. Określ też, że Twoja aktywność obsługuje zmiany konfiguracji układu, tak by aktywność nie uruchamiała się ponownie po zmianie układu podczas przejścia w tryb PIP.

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

Przełącz aktywność na tryb PIP

Począwszy od Androida 12 możesz przełączyć aktywność w tryb obrazu w obrazie, ustawiając flagę setAutoEnterEnabled na true. Przy tym ustawieniu aktywność automatycznie przełącza się w tryb obrazu w obrazie w razie potrzeby bez konieczności wywoływania funkcji enterPictureInPictureMode() w onUserLeaveHint. Dodatkową zaletą jest znacznie płynniejsze przejścia. Szczegółowe informacje znajdziesz w sekcji Płynniejsze przechodzenie do trybu obrazu w obrazie z nawigacji przy użyciu gestów.

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

Kotlin

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

Java

@Override
public void onActionClicked(Action action) {
    if (action.getId() == R.id.lb_control_picture_in_picture) {
        getActivity().enterPictureInPictureMode();
        return;
    }
    ...
}

Możesz użyć logiki, która przełącza aktywność w tryb PIP, zamiast przechodzić w tle. Na przykład Mapy Google przełączają się w tryb obrazu w obrazie, gdy użytkownik naciśnie w trakcie nawigacji przycisk ekranu głównego lub ostatnio używanych. Aby przechwycić to zgłoszenie, zastąp parametr onUserLeaveHint():

Kotlin

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

Java

@Override
public void onUserLeaveHint () {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode();
    }
}

Zalecane: zapewnij użytkownikom dopracowany obraz w obrazie w obrazie

W Androidzie 12 wprowadzono istotne poprawki kosmetyczne w zakresie animowanych przejść między oknami trybu pełnoekranowego i obrazu w obrazie. Zdecydowanie zalecamy wprowadzenie wszystkich obowiązujących zmian. Gdy to zrobisz, będą one automatycznie skalować się na duże ekrany, np. urządzenia składane i tablety, i nie musisz już nic robić.

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

Są to między innymi:

  • Płynniejsze przejścia do trybu obrazu w obrazie z nawigacji przy użyciu gestów
  • Ustawianie odpowiedniego parametru sourceRectHint do włączania i wyłączania trybu obrazu w obrazie
  • Wyłączanie płynnego zmieniania rozmiaru treści innych niż filmy

Aby dowiedzieć się, jak uzyskać efekt przejścia, zapoznaj się z przykładem korzystania z funkcji Android Kotlin PictureInPicture w języku angielskim.

Płynniejsze przejścia do trybu obrazu w obrazie dzięki nawigacji przy użyciu gestów

Począwszy od Androida 12 flaga setAutoEnterEnabled zapewnia znacznie płynniejszą animację podczas przechodzenia do treści wideo w trybie PIP za pomocą nawigacji przy użyciu gestów, na przykład podczas przesuwania palcem w górę z trybu pełnoekranowego w górę na ekran główny.

Aby wprowadzić tę zmianę, wykonaj te czynności i zapoznaj się z tym przykładem:

  1. Użyj setAutoEnterEnabled do utworzenia PictureInPictureParams.Builder:

    Kotlin

    setPictureInPictureParams(PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build())
    

    Java

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. Zadzwoń do firmy setPictureInPictureParams i przekaż aktualne informacje PictureInPictureParams wcześniej. Aplikacja nie czeka na wywołanie zwrotne onUserLeaveHint (tak jak w Androidzie 11).

    Możesz na przykład wywoływać setPictureInPictureParams przy pierwszym odtworzeniu i w przypadku kolejnych, jeśli zmieni się format obrazu.

  3. Zadzwoń pod numer setAutoEnterEnabled(false), ale tylko wtedy, gdy jest to konieczne. Na przykład prawdopodobnie nie chcesz włączać funkcji PIP, jeśli bieżące odtwarzanie jest wstrzymane.

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

Począwszy od wprowadzenia funkcji obraz w obrazie w Androidzie 8.0 ikona setSourceRectHint oznacza obszar działania, który jest widoczny po przejściu do obrazu w obrazie – na przykład granice wyświetlania filmu w odtwarzaczu.

W Androidzie 12 system używa sourceRectHint do implementowania znacznie płynniejszej animacji zarówno przy włączaniu, jak i wyjściu z trybu obrazu w obrazie.

Aby poprawnie skonfigurować obraz sourceRectHint pod kątem włączania i wyłączania trybu obrazu w obrazie:

  1. Utwórz obiekt PictureInPictureParams, korzystając z odpowiednich granic jako sourceRectHint. Zalecamy też dołączenie do odtwarzacza odbiornika zmian układu:

    Kotlin

    val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, oldLeft: Int,
            oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop:
            Int, newRight: Int, newBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
    

    Java

    private final View.OnLayoutChangeListener mOnLayoutChangeListener =
            (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight,
            newBottom) -> {
        final Rect sourceRectHint = new Rect();
        mYourVideoView.getGlobalVisibleRect(sourceRectHint);
        final PictureInPictureParams.Builder builder =
            new PictureInPictureParams.Builder()
                .setSourceRectHint(sourceRectHint);
        setPictureInPictureParams(builder.build());
    };
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
    
  2. W razie potrzeby zaktualizuj sourceRectHint, zanim system rozpocznie przenoszenie subskrypcji. Gdy system ma wyjść z trybu obrazu w obrazie, hierarchia widoków działania jest ustawiana zgodnie z konfiguracją docelową (np. na pełnym ekranie). Aplikacja może dołączyć detektor zmian układu do widoku głównego lub docelowego (np. widoku odtwarzacza wideo), aby wykryć zdarzenie i zaktualizować sourceRectHint przed rozpoczęciem animacji.

    Kotlin

    // 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()
            )
        }
    }
    
    

    Java

    // Listener is called right after the user exits PiP but before
    // animating.
    playerView.addOnLayoutChangeListener((v, 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.
            final Rect sourceRectHint = new Rect();
            playerView.getGlobalVisibleRect(sourceRectHint);
            setPictureInPictureParams(
                new PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build());
        }
    });
    
    

Wyłącz płynne zmienianie rozmiaru treści innych niż filmy

W Androidzie 12 dodano flagę setSeamlessResizeEnabled, która zapewnia płynniejszą animację przenikania podczas zmiany rozmiaru treści innych niż filmy w oknie obrazu w obrazie. Wcześniej zmiana rozmiaru treści innych niż filmy w oknie PIP może powodować irytujące artefakty wizualne.

Aby wyłączyć płynną zmianę rozmiaru w przypadku treści innych niż filmy:

Kotlin

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

Java

setPictureInPictureParams(new PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build());

Obsługa UI podczas PIP

Gdy działanie przejdzie w tryb obraz w obrazie lub z niego wyjdzie, system wywoła metodę Activity.onPictureInPictureModeChanged() lub Fragment.onPictureInPictureModeChanged().

Należy zastąpić te wywołania zwrotne, aby ponownie narysować elementy interfejsu aktywności. Pamiętaj, że w trybie obraz w obrazie Twoja aktywność wyświetla się w małym oknie. W trybie obrazu w obrazie użytkownicy nie mogą wchodzić w interakcje z elementami interfejsu aplikacji, a drobne elementy interfejsu mogą być słabo widoczne. Działania związane z odtwarzaniem filmów przy minimalnym interfejsie zapewniają użytkownikom najlepsze wrażenia.

Jeśli Twoja aplikacja musi udostępniać niestandardowe działania na potrzeby funkcji PIP, zapoznaj się z sekcją Dodawanie elementów sterujących na tej stronie. Usuń inne elementy interfejsu, zanim aktywność znajdzie się w obrazie w obrazie, i przywróć je po ponownym przejściu do trybu pełnoekranowego:

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in PiP mode.
    } else {
        // Restore the full-screen UI.
    }
}

Java

@Override
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in PiP mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

Dodaj elementy sterujące

Okno PiP może wyświetlać elementy sterujące, gdy użytkownik otworzy menu okna (przez kliknięcie okna na urządzeniu mobilnym lub wybranie menu na pilocie telewizora).

Jeśli aplikacja ma aktywną sesję multimediów, pojawią się elementy sterujące Odtwórz, Wstrzymaj, Dalej i Poprzednie.

Możesz też bezpośrednio określić działania niestandardowe, tworząc parametry PictureInPictureParams za pomocą PictureInPictureParams.Builder.setActions(), zanim włączysz tryb obrazu w obrazie, i przekazać je w trybie PIP 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 PIP

Gdy aktywność przełączy się na tryb PIP, system przełączy ją w stan wstrzymania i wywoła metodę onPause(). Jeśli odtwarzanie filmu zostało wstrzymane w trybie PIP, nie powinno się ono wstrzymywać. Należy je kontynuować.

W Androidzie 7.0 i nowszych należy wstrzymywać i wznawiać odtwarzanie filmu, gdy system wywoła polecenia onStop() i onStart() Twojej aktywności. Dzięki temu unikniesz sprawdzania za pomocą funkcji onPause(), czy aplikacja jest w trybie PIP, i kontynuowania odtwarzania.

Jeśli flaga setAutoEnterEnabled nie jest ustawiona na true i musisz wstrzymać odtwarzanie w implementacji onPause(), sprawdź tryb obrazu w obrazie, wywołując isInPictureInPictureMode() i odpowiednio obsługiwać odtwarzanie. Na przykład:

Kotlin

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

Java

@Override
public void onPause() {
    // If called while in PiP mode, do not pause playback
    if (isInPictureInPictureMode()) {
        // Continue playback
        ...
    } else {
        // Use existing playback logic for paused Activity behavior.
        ...
    }
}

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

Używanie jednego rodzaju odtwarzania w przypadku funkcji PIP

Podczas przeglądania treści na ekranie głównym użytkownik może wybrać nowy film, podczas gdy odtwarzanie wideo działa w trybie PIP. Odtwarzaj nowy film w bieżącym trybie odtwarzania w trybie pełnoekranowym, zamiast uruchamiać nową aktywność, która może zdezorientować użytkownika.

Aby mieć pewność, że pojedyncza aktywność będzie wykorzystywana w żądaniach odtwarzania filmu i w razie potrzeby włączyć lub wyłączyć tryb obraz w obrazie, ustaw w pliku manifestu parametr android:launchMode aktywności na singleTask:

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

W swojej aktywności zastąp onNewIntent() i obsługuj nowy film. W razie potrzeby zatrzymaj odtwarzanie istniejącego.

Sprawdzone metody

Funkcja PiP może być wyłączona na urządzeniach z małą ilością pamięci RAM. Zanim aplikacja zacznie używać funkcji PIP, upewnij się, że jest dostępna, wywołując metodę hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

Funkcja PIP jest przeznaczona dla czynności, podczas których odtwarzane są filmy na pełnym ekranie. Gdy przełączysz aktywność w tryb PIP, nie pokazuj niczego poza filmami. Możesz śledzić, kiedy aktywność przechodzi w tryb obrazu w obrazie i gdy ukrywasz elementy interfejsu, zgodnie z opisem w sekcji Obsługa UI w trakcie tego trybu.

Gdy aktywność jest w trybie obraz w obrazie, domyślnie nie jest zaznaczone jako rodzaj danych wejściowych. Aby odbierać zdarzenia wejściowe w trybie obrazu w obrazie, użyj funkcji MediaSession.setCallback(). Więcej informacji o korzystaniu z setCallback() znajdziesz w artykule Wyświetlanie karty Co jest grane.

Gdy aplikacja jest w trybie PIP, odtwarzanie filmu w tym oknie może powodować zakłócenia dźwięku w innej aplikacji, takiej jak odtwarzacz muzyki lub aplikacja do wyszukiwania głosowego. Aby tego uniknąć, przed rozpoczęciem odtwarzania filmu ustaw żądanie aktywnego trybu audio i obsługę powiadomień o zmianie ostrości dźwięku zgodnie z opisem w sekcji Zarządzanie aktywnością audio. Jeśli w trybie obraz w obrazie otrzymasz powiadomienie o utracie ostrości dźwięku, wstrzymaj lub zatrzymaj odtwarzanie filmu.

Gdy aplikacja ma wkrótce włączyć obraz w obrazie, pamiętaj, że do trybu obrazu w obrazie dołączana jest tylko najpopularniejsza aktywność. W niektórych sytuacjach, np. na urządzeniach z wieloma oknami, może się zdarzyć, że poniższa aktywność będzie teraz widoczna i ponownie widoczna obok aktywności PiP. Należy zastosować się do tej sytuacji, m.in. aby uzyskać wywołanie zwrotne onResume() lub onPause(). Użytkownik może też wejść w interakcję z aktywnością. Jeśli na przykład wyświetlana jest aktywność na liście filmów, a aktywność związana z odtwarzaniem filmów w trybie obraz w obrazie, użytkownik może wybrać z listy nowy film, a aktywność PIP powinna być aktualizowana zgodnie z oczekiwaniami.

Dodatkowy przykładowy kod

Aby pobrać przykładową aplikację napisaną w Androidzie, zobacz Obraz w obrazie. Aby pobrać przykładową aplikację napisaną w Kotlin, przeczytaj artykuł o Android PictureInPicture Sample (Kotlin).