Korzystanie z biblioteki Jetpack Picture-in-Picture

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

Biblioteka Jetpack PiP ułatwia korzystanie z dotychczasowych 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 PiP w różnych wersjach Androida, np. używa enterPictureInPictureMode przed Androidem 12 i isAutoEnterEnabled po nim, więc programiści nie muszą zarządzać różnicami między wersjami.
  • Nieprawidłowe parametry PiP: zapewnia ujednolicone rozwiązanie do prawidłowego ustawiania parametrów PiP, np. setSourceRectHint, aby tworzyć płynne i wysokiej jakości animacje podczas odtwarzania multimediów.
  • Ujednolicone wywołania zwrotne stanu PiP: łączy onPictureInPictureModeChangedonPictureInPictureUiStateChanged w jeden ujednolicony interfejs wywołania zwrotnego (PictureInPictureDelegate.OnPictureInPictureEventListener), co upraszcza zarządzanie stanem i interfejsem.
  • Ograniczenie ilości powtarzalnego kodu: biblioteka zmniejsza ilość powtarzalnego kodu, oferując predefiniowane 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 PiP są dostarczane za pomocą biblioteki Jetpack, co pozwala użytkownikom uzyskiwać dostęp do dodatkowych funkcji przy minimalnym lub zerowym wysiłku.

Przepływ migracji

Określ kategorię przypadku użycia aplikacji i starszą logikę PiP:

Kategorie: Odtwarzanie wideo, Nawigacja lub Rozmowa wideo.

Starsza logika PiP do identyfikacji:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. Konfiguracja pliku AndroidManifest

Sprawdź, czy aktywność wchodząca w tryb obrazu w obrazie deklaruje obsługę w AndroidManifest.xml z odpowiednim 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") }

W przypadku zależności używaj najnowszych bibliotek AndroidX. Informacje na ten temat znajdziesz na stronie wersji.

4. Wybór i inicjowanie szablonu

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

  • Nawigacja i rozmowy wideo: BasicPictureInPicture; zmiana rozmiaru bez przerw nie jest zwykle obsługiwana i nie potrzebujesz wskazówki dotyczącej prostokąta źródłowego.
  • Odtwarzanie filmu: VideoPlaybackPictureInPicture; automatycznie śledzi granice widoku odtwarzacza w przypadku wskazówki dotyczącej prostokąta źródłowego i domyślnie umożliwia płynną zmianę rozmiaru.

Aby wdrożyć bibliotekę Jetpack, zastąp istniejącą niestandardową implementację PiP interfejsami API biblioteki Jetpack. Złożoność i koszt wdrożenia będą się różnić w zależności od obecnej implementacji aplikacji.

W sekcjach poniżej opisujemy typowe przypadki użycia trybu obrazu w obrazie i niezbędne kroki wdrożenia:

Aplikacja informuje bibliotekę o stanie aktywności lub nieaktywności nawigacji i ustawia współczynnik proporcji. Resztą zajmie się biblioteka Jetpack.

Najważniejsze różnice:

  1. Nie musisz rozróżniać automatycznego i starszego sposobu wprowadzania danych po stronie aplikacji.
  2. Skonsolidowane interfejsy wywołań zwrotnych.
  3. Nowy konstruktor PictureInPictureParams zapewniający zgodność wsteczną.

Rozmowa wideo

Aplikacja informuje bibliotekę o stanie połączenia (aktywne lub nieaktywne) i ustawia współczynnik proporcji.

Najważniejsze różnice:

  1. Nie musisz rozróżniać automatycznego i starszego sposobu wprowadzania danych po stronie aplikacji.
  2. Skonsolidowane interfejsy wywołań zwrotnych.
  3. Nowy konstruktor PictureInPictureParams zapewniający zgodność wsteczną.
  4. Standardowe ikony działań w przypadku rozmów wideo.

5. Migracja kodu

  • Logika wejścia: zastąp logikę specyficzną dla interfejsu API, np. setAutoEnterEnabledw przypadku Androida 12 i nowszych wersji lub onUserLeaveHintw przypadku Androida 11 i starszych wersji, kodem setEnabled. Wywołuj go za każdym razem, gdy zmieni się stan kwalifikacji do trybu obrazu w obrazie.
  • Wywołania zwrotne: połącz 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ą funkcji setActionssetAspectRatio w instancji szablonu, gdy tylko się zmienią.

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