Korzystanie z biblioteki Jetpack Picture-in-Picture

Biblioteka Jetpack obrazu w obrazie to uproszczone i niezawodne rozwiązanie dla deweloperów aplikacji na Androida, które umożliwia implementację funkcji obrazu w obrazie, zwłaszcza w przypadku aplikacji do odtwarzania multimediów, komunikacji wideo i nawigacji. Dzięki udostępnieniu ujednoliconego interfejsu API biblioteka pomaga wyeliminować powtarzalny kod i typowe błędy w aplikacji oraz poprawić ogólną jakość korzystania z funkcji obrazu w obrazie.

Biblioteka Jetpack PiP ułatwia korzystanie z istniejących interfejsów API PiP, rozwiązując kilka kluczowych problemów i niezgodności w ekosystemie Androida:

  • Fragmentacja systemu operacyjnego: biblioteka automatycznie obsługuje różnice w wywołaniach interfejsu API obrazu w obrazie w różnych wersjach Androida, np. używa enterPictureInPictureMode przed Androidem 12 i isAutoEnterEnabled po nim, dzięki czemu deweloperzy nie muszą zarządzać różnicami między wersjami.
  • Nieprawidłowe parametry obrazu w obrazie: zapewnia ujednolicone rozwiązanie do prawidłowego ustawiania parametrów obrazu w obrazie, np. setSourceRectHint, aby tworzyć płynne i wysokiej jakości animacje podczas odtwarzania multimediów.
  • Ujednolicone wywołania zwrotne stanu PiP: konsoliduje onPictureInPictureModeChanged i onPictureInPictureUiStateChanged w jeden ujednolicony interfejs wywołania zwrotnego (PictureInPictureDelegate.OnPictureInPictureEventListener), co upraszcza zarządzanie stanem i interfejsem.
  • Mniej powtarzalnego kodu: biblioteka zmniejsza ilość powtarzalnego kodu, oferując wstępnie zdefiniowane zestawy RemoteActions do typowych zastosowań, takich jak elementy sterujące odtwarzaniem i działania związane z rozmowami wideo.
  • Przyszłościowe rozwiązanie: kolejne funkcje obrazu w obrazie są dostarczane przez bibliotekę Jetpack, co pozwala użytkownikom na dostęp do dodatkowych funkcji przy minimalnym wysiłku.

Przepływ migracji

Określ kategorię przypadku użycia aplikacji i starszą logikę obrazu w obrazie:

Kategorie: odtwarzanie filmów, nawigacja lub rozmowa wideo.

Starsza logika PiP do identyfikacji:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. Konfiguracja pliku AndroidManifest

Upewnij się, że aktywność, która przechodzi w tryb obrazu w obrazie, deklaruje obsługę w pliku AndroidManifest.xml z odpowiednim elementem configChanges, aby zapobiec niepotrzebnym ponownym uruchomieniom:

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

3. Konfiguracja środowiska

Dodaj wymagane zależności do pliku build.gradle:

dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }

Używaj najnowszych bibliotek AndroidX w zależnościach. Informacje o nich znajdziesz na stronie Wersje.

4. Wybór i inicjowanie szablonu

Wybierz szablon implementacji, który najlepiej pasuje do przypadku użycia aplikacji:

  • Nawigacja i rozmowa wideo: BasicPictureInPicture; płynna zmiana rozmiaru nie jest zwykle obsługiwana i nie potrzebujesz wskazówki dotyczącej prostokąta źródłowego.
  • Odtwarzanie filmów: VideoPlaybackPictureInPicture automatycznie śledzi granice widoku odtwarzacza, aby uzyskać wskazówkę dotyczącą prostokąta źródłowego, i domyślnie umożliwia płynną zmianę rozmiaru.

Aby zastosować bibliotekę Jetpack, zastąp dotychczasową niestandardową implementację obrazu w obrazie interfejsami API biblioteki Jetpack. Złożoność i koszt wdrożenia będą się różnić w zależności od bieżącej implementacji aplikacji.

W poniższych sekcjach opisujemy niektóre typowe przypadki użycia obrazu w obrazie i niezbędne kroki implementacji:

Aplikacja informuje bibliotekę o aktywnym lub nieaktywnym stanie nawigacji i ustawia format obrazu. Resztą zajmuje się biblioteka Jetpack.

Najważniejsze różnice:

  1. Nie trzeba rozróżniać automatycznego i starszego sposobu wprowadzania po stronie aplikacji.
  2. Skonsolidowane interfejsy wywołania zwrotnego.
  3. Nowy kreator PictureInPictureParams zapewniający zgodność wsteczną.

Rozmowa wideo

Aplikacja informuje bibliotekę o aktywnym lub nieaktywnym stanie połączenia i ustawia format obrazu.

Najważniejsze różnice:

  1. Nie trzeba rozróżniać automatycznego i starszego sposobu wprowadzania po stronie aplikacji.
  2. Skonsolidowane interfejsy wywołania zwrotnego.
  3. Nowy kreator PictureInPictureParams zapewniający zgodność wsteczną.
  4. Standaryzowane ikony działań podczas rozmowy wideo.

5. Migracja kodu

  • Logika wejścia: zastąp logikę interfejsu API, np. setAutoEnterEnabled w przypadku Androida 12 i nowszych wersji lub onUserLeaveHint w przypadku Androida 11 i starszych wersji, za pomocą setEnabled. Uruchamiaj to zdarzenie za każdym razem, gdy zmieni się stan uprawnień do korzystania z trybu obrazu w obrazie.
  • Wywołania zwrotne: skonsoliduj onPictureInPictureModeChanged (przełączanie układu) i onPictureInPictureUiStateChanged (animacja/stany) w ujednolicone wywołanie zwrotne oparte na zdarzeniach onPictureInPictureEvent.
  • Działania i parametry: aktualizuj parametry za pomocą setActions i setAspectRatio w instancji szablonu, gdy tylko ulegną zmianie.

Wzorce implementacji referencyjnej

Przykłady implementacji.

Nawigacja i rozmowa wideo

class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: BasicPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = BasicPictureInPicture(this)
        // BasicPictureInPicture is ideal for Navigation and Video call use cases.
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ }
        }
    }
}

Odtwarzanie filmu

class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = VideoPlaybackPictureInPicture(this)
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
            ContentScreen(pictureInPictureImpl)
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ }
            PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ }
            PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ }
        }
    }

    @Composable
    fun ContentScreen(pipController: VideoPlaybackPictureInPicture) {
        DisposableEffect(pipController) {
            onDispose {
                pipController.close()
            }
        }
    }
}