Используйте библиотеку «Картинка в картинке» Jetpack.

Библиотека Jetpack для режима «картинка в картинке» (PiP) предлагает оптимизированное и надежное решение для разработчиков Android-приложений, позволяющее реализовать функциональность PiP, особенно для воспроизведения мультимедиа, видеосвязи и навигации. Предоставляя единый API, библиотека помогает устранить шаблонный код, распространенные ошибки в приложениях и улучшить общее качество пользовательского опыта в режиме PiP.

Библиотека PiP Jetpack упрощает работу с существующими API PiP, устраняя ряд ключевых проблем и несоответствий в экосистеме Android:

  • Фрагментация ОС : Библиотека автоматически обрабатывает различия в вызовах API PiP в разных версиях Android, например, использует enterPictureInPictureMode до Android 12 и isAutoEnterEnabled после, поэтому разработчикам не нужно управлять различиями в версиях.
  • Неправильные параметры PiP : Предоставляется унифицированное решение для правильной настройки параметров PiP, например, setSourceRectHint , для создания плавной и высококачественной анимации во время воспроизведения мультимедиа.
  • Единый интерфейс обратного вызова для управления состоянием PiP : он объединяет события onPictureInPictureModeChanged и onPictureInPictureUiStateChanged в единый интерфейс обратного вызова ( PictureInPictureDelegate.OnPictureInPictureEventListener ) для упрощения управления состоянием и пользовательским интерфейсом.
  • Сокращение количества повторяющегося шаблонного кода : библиотека уменьшает объем повторяющегося шаблонного кода, предлагая предопределенные наборы RemoteActions для распространенных сценариев использования, таких как управление воспроизведением и действия при видеозвонках.
  • Перспективная реализация : Дополнительные функции PiP (картинка в картинке) предоставляются через библиотеку Jetpack, что позволяет пользователям получать доступ к дополнительной функциональности с минимальными усилиями или без них.

Процесс миграции

Определите категорию использования приложения и устаревшую логику режима «картинка в картинке»:

Категории: Воспроизведение видео, Навигация или Видеозвонок.

Необходимо идентифицировать устаревшую логику PiP:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams .

2. Конфигурация AndroidManifest

Убедитесь, что Activity, входящая в режим PiP, указывает поддержку этой функции в файле AndroidManifest.xml с необходимыми configChanges , чтобы предотвратить ненужные перезапуски:

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

3. Настройка среды

Добавьте необходимые зависимости в 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") }

Для зависимостей используйте последние версии библиотек AndroidX, подробную информацию см. на странице релизов .

4. Выбор и инициализация шаблона

Выберите шаблон реализации, который наилучшим образом соответствует сценарию использования приложения:

  • Навигация и видеозвонок : BasicPictureInPicture ; плавное изменение размера обычно не поддерживается, и вам не нужна подсказка для исходного прямоугольника.
  • Воспроизведение видео : VideoPlaybackPictureInPicture ; автоматически отслеживает границы области просмотра проигрывателя для подсказки исходного прямоугольника и по умолчанию включает плавное изменение размера.

Для внедрения библиотеки Jetpack замените существующую пользовательскую реализацию режима «картинка в картинке» на API библиотеки Jetpack. Сложность и стоимость внедрения будут варьироваться в зависимости от текущей реализации приложения.

В следующих разделах описаны некоторые типичные сценарии использования PiP и необходимые этапы реализации:

Приложение сообщает библиотеке об активном или неактивном состоянии навигации и устанавливает соотношение сторон. Библиотека Jetpack обрабатывает остальное.

Ключевые отличия:

  1. Нет необходимости различать автоматический ввод и ввод с помощью устаревших функций на стороне приложения.
  2. Объединенные интерфейсы обратного вызова.
  3. Новый конструктор PictureInPictureParams для обеспечения обратной совместимости.

Видеозвонок

Приложение сообщает библиотеке об активном или неактивном состоянии вызова и устанавливает соотношение сторон.

Ключевые отличия:

  1. Нет необходимости различать автоматический ввод и ввод с помощью устаревших функций на стороне приложения.
  2. Объединенные интерфейсы обратного вызова.
  3. Новый конструктор PictureInPictureParams для обеспечения обратной совместимости.
  4. Стандартизированные значки действий для видеозвонка.

5. Миграция кода

  • Логика входа: Замените специфичную для API логику, такую ​​как setAutoEnterEnabled для Android 12 и выше или onUserLeaveHint для Android 11 и ниже, на setEnabled . Запускайте это всякий раз, когда изменяется статус доступности режима «картинка в картинке».
  • Обратные вызовы: Объединить onPictureInPictureModeChanged (переключение макета) и onPictureInPictureUiStateChanged (анимация/состояния) в единый обработчик событий onPictureInPictureEvent .
  • Действия и параметры: Обновляйте параметры с помощью setActions и setAspectRatio в экземпляре шаблона всякий раз, когда они изменяются.
  • Специальная обработка видео: Для видеоприложений используйте setPlayerView для автоматического обновления подсказок исходного прямоугольника и обеспечения плавных переходов. ` ### 6. Очистка

Для VideoPlaybackPictureInPicture вызовите метод close в onDispose или onDestroy , чтобы освободить ресурсы, такие как трекеры просмотра.

Эталонные шаблоны реализации

Примеры реализаций.

Навигация и видеозвонок

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 */ }
        }
    }
}

Воспроизведение видео

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