Как использовать режим "Картинка в картинке"

Попробуйте Compose
Jetpack Compose – это рекомендуемый набор инструментов для создания пользовательского интерфейса в Android. Узнайте, как поддерживать картинку в картинке в Compose.

Начиная с Android 8.0 (уровень API 26), в этой ОС поддерживается режим "картинка в картинке". Режим "картинка в картинке" – это особый тип многооконного режима, который используется для воспроизведения видео, видеовызовов и навигации. Она позволяет закрепить окно текущего действия в углу экрана и переключаться между приложениями или просматривать контент на основном экране.

Для создания закрепленного окна с видео в режиме "картинка в картинке" используются API многооконного режима, доступные в Android 7.0. Чтобы добавить режим "Картинка в картинке" в приложение, зарегистрируйте объекты activity, которые поддерживают этот режим, переключите объект activity в режим "Картинка в картинке" и убедитесь, что элементы интерфейса скрыты, а воспроизведение видео продолжается, когда объект activity находится в режиме "Картинка в картинке".

Окно "Картинка в картинке" появляется в верхнем слое экрана в углу, выбранном системой.

Режим "Картинка в картинке" также поддерживается на совместимых устройствах Android TV OS с Android 14 (уровень API 34) или более поздней версии. Хотя у этих режимов много общего, при использовании картинки в картинке на телевизоре нужно учитывать дополнительные факторы.

Как пользователи могут взаимодействовать с окном в режиме "картинка в картинке"

Пользователи могут перетаскивать окно в режиме "картинка в картинке" в другое место. Начиная с Android 12 пользователи также могут:

  • Нажмите на окно один раз, чтобы отобразить переключатель полноэкранного режима, кнопку закрытия, кнопку настроек и настраиваемые действия, предоставляемые вашим приложением (например, элементы управления воспроизведением).

  • Дважды нажмите на окно, чтобы переключиться между текущим размером и максимальным или минимальным. Например, если дважды нажать на развернутое окно, оно свернется, и наоборот.

  • Перетащите окно к левому или правому краю экрана. Чтобы вернуть окно на экран, нажмите на его видимую часть или перетащите его.

  • Изменить размер окна можно, сведя или разведя пальцы на экране.

Приложение управляет тем, когда текущее действие переходит в режим "Картинка в картинке". Вот несколько примеров:

  • Активность может перейти в режим PiP, когда пользователь нажимает кнопку главного экрана или проводит пальцем вверх, чтобы перейти на главный экран. Именно поэтому Google Карты продолжают показывать маршруты, даже когда пользователь одновременно выполняет другие действия.

  • Приложение может переключаться в режим "Картинка в картинке", когда пользователь возвращается к просмотру другого контента.

  • Приложение может переключить видео в режим "Картинка в картинке", когда пользователь досматривает серию. На главном экране показывается рекламная или краткая информация о следующей серии.

  • В приложении можно реализовать возможность добавлять контент в очередь во время просмотра видео. Видео продолжает воспроизводиться в режиме "картинка в картинке", а на основном экране отображается выбор контента.

Как объявить о поддержке режима "Картинка в картинке"

По умолчанию система не поддерживает режим "Картинка в картинке" для приложений. Если вы хотите, чтобы ваше приложение поддерживало режим "Картинка в картинке", зарегистрируйте в манифесте действие с видео, установив для android:supportsPictureInPicture значение true. Также укажите, что ваше действие обрабатывает изменения конфигурации макета, чтобы оно не перезапускалось при изменении макета во время перехода в режим "Картинка в картинке".

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

Реализация режима «картинка в картинке» с помощью Jetpack.

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

,

Реализация режима «картинка в картинке» с помощью Jetpack.

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

Как переключить действие в режим "Картинка в картинке"

Начиная с Android 12, вы можете переключить действие в режим "картинка в картинке", установив для флага setAutoEnterEnabled значение true. При этом объект activity автоматически переключается в режим "картинка в картинке" без необходимости явного вызова enterPictureInPictureMode() в onUserLeaveHint. И это имеет дополнительное преимущество, обеспечивая гораздо более плавные переходы. Подробнее о том, как сделать переходы в режим "картинка в картинке" более плавными при навигации жестами…

Если вы настраиваете таргетинг на Android 11 или более ранние версии, для перехода в режим "Картинка в картинке" необходимо вызвать enterPictureInPictureMode(). Например, следующий код переключает активность в режим "картинка в картинке", когда пользователь нажимает специальную кнопку в интерфейсе приложения:

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

Вы можете включить логику, которая переключает действие в режим PiP вместо перехода в фоновый режим. Например, Google Карты переключаются в режим "картинка в картинке", если пользователь нажимает кнопку "Домой" или "Недавние" во время навигации. Вы можете отследить этот случай, переопределив onUserLeaveHint():

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

Рекомендуется: обеспечьте пользователям плавный переход в режим "картинка в картинке"

В Android 12 были значительно улучшены анимированные переходы между полноэкранным режимом и режимом "картинка в картинке". Мы настоятельно рекомендуем реализовать все применимые изменения. После этого они будут автоматически масштабироваться для больших экранов, например складных устройств и планшетов, без каких-либо дополнительных действий.

Если вы не добавите в приложение необходимые обновления, переходы в режим "Картинка в картинке" будут работать, но анимация будет менее плавной. Например, при переходе из полноэкранного режима в режим "картинка в картинке" окно может исчезнуть, а затем снова появиться, когда переход будет завершен.

Изменения описаны ниже.

  • Переход в режим «Картинка в картинке» с помощью навигации жестами стал более плавным.
  • Настройка правильного значения sourceRectHint для входа в режим "Картинка в картинке" и выхода из него.
  • Как отключить плавное изменение размера для контента, не являющегося видео

Чтобы переход был плавным, воспользуйтесь примером PictureInPicture для Android Kotlin.

Сделайте переход в режим "Картинка в картинке" более плавным при навигации жестами

Начиная с Android 12 флаг setAutoEnterEnabled обеспечивает более плавную анимацию при переходе к видеоконтенту в режиме PiP с помощью навигации жестами, например при свайпе вверх для перехода на главный экран из полноэкранного режима.

Чтобы внести это изменение, выполните следующие действия:

  1. Используйте setAutoEnterEnabled, чтобы создать 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. Вызовите setPictureInPictureParams с актуальным PictureInPictureParams как можно раньше. Приложение не ждет обратного вызова onUserLeaveHint, как это было в Android 11.

    Например, вы можете вызывать setPictureInPictureParams при первом воспроизведении и при любом последующем, если соотношение сторон изменилось.

  2. Вызывайте setAutoEnterEnabled(false) только при необходимости. Например, вы, вероятно, не захотите входить в режим PiP, если текущее воспроизведение находится в приостановленном состоянии.

Задайте подходящее значение sourceRectHint для входа в режим "Картинка в картинке" и выхода из него.

Начиная с Android 8.0, когда был представлен режим "Картинка в картинке", setSourceRectHint указывает область действия, которая видна после перехода в этот режим, например границы видео в проигрывателе.

В Android 12 система использует sourceRectHint, чтобы сделать анимацию при переходе в режим "Картинка в картинке" и выходе из него более плавной.

Чтобы правильно настроить sourceRectHint для входа в режим "Картинка в картинке" и выхода из него:

  1. Создайте PictureInPictureParams, используя правильные границы, например sourceRectHint. Также рекомендуем добавить в видеопроигрыватель прослушиватель изменения макета:
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. При необходимости обновите sourceRectHint до того, как система начнет переход к выходу. Когда система собирается выйти из режима "картинка в картинке", иерархия View действия размещается в соответствии с целевой конфигурацией (например, полноэкранный режим). Приложение может прикрепить прослушиватель изменения макета к корневому или целевому представлению (например, представлению видеопроигрывателя), чтобы обнаружить событие и обновить sourceRectHint до начала анимации.
// 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()
        )
    }
}

Отключить плавное изменение размера для невидеоконтента

В Android 12 добавлен флаг setSeamlessResizeEnabled, который обеспечивает более плавную анимацию перекрестного затухания при изменении размера невидеоконтента в окне "Картинка в картинке". Ранее при изменении размера окна "картинка в картинке" с невидеоконтентом могли возникать визуальные артефакты.

Чтобы включить плавное изменение размера видеоконтента, выполните следующие действия:

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

Как управлять интерфейсом в режиме "Картинка в картинке"

Когда объект activity переходит в режим "картинка в картинке" или выходит из него, система вызывает метод Activity.onPictureInPictureModeChanged() или Fragment.onPictureInPictureModeChanged().

В Android 15 переход в режим "Картинка в картинке" стал ещё более плавным. Это полезно для приложений, у которых есть элементы интерфейса, наложенные поверх основного интерфейса, который переходит в режим "картинка в картинке".

Разработчики используют обратный вызов onPictureInPictureModeChanged(), чтобы определить логику, которая переключает видимость наложенных элементов интерфейса. Этот обратный вызов активируется, когда завершается анимация входа в режим "картинка в картинке" или выхода из него. Начиная с Android 15, класс PictureInPictureUiState включает новое состояние.

В приложениях, предназначенных для Android 15, обратный вызов Activity#onPictureInPictureUiStateChanged() с isTransitioningToPip() выполняется сразу после начала анимации перехода в режим "картинка в картинке". Многие элементы интерфейса не нужны, когда приложение работает в режиме "Картинка в картинке". Например, это могут быть представления или макеты с информацией о рекомендациях, следующем видео, оценках и названиях. Когда приложение переходит в режим "Картинка в картинке", используйте обратный вызов onPictureInPictureUiStateChanged(), чтобы скрыть эти элементы интерфейса. Когда приложение переходит в полноэкранный режим из окна "Картинка в картинке", используйте обратный вызов onPictureInPictureModeChanged(), чтобы снова показать эти элементы, как показано в следующих примерах:

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.
    }
}

Быстрое переключение видимости нерелевантных элементов пользовательского интерфейса (для окна PIP) помогает обеспечить более плавную анимацию перехода в режим PIP без мерцания.

Переопределите эти обратные вызовы, чтобы перерисовать элементы пользовательского интерфейса операции. Помните, что в режиме "Картинка в картинке" ваши действия показываются в небольшом окне. В режиме "Картинка в картинке" пользователи не могут взаимодействовать с элементами интерфейса приложения, а мелкие детали могут быть плохо видны. Для удобства пользователей в действиях по воспроизведению видео должен быть минимальный интерфейс.

Если в вашем приложении нужно предоставить специальные действия для режима "Картинка в картинке", ознакомьтесь с разделом Добавление элементов управления на этой странице. Удалите другие элементы интерфейса, прежде чем ваше действие перейдет в режим "картинка в картинке", и восстановите их, когда действие снова станет полноэкранным.

Добавить элементы управления

В окне "Картинка в картинке" могут отображаться элементы управления, когда пользователь открывает меню окна (нажав на окно на мобильном устройстве или выбрав меню на пульте телевизора).

Если в приложении есть активный медиасеанс, появятся элементы управления воспроизведением: "Воспроизвести", "Пауза", "Далее" и "Назад".

Вы также можете явно указать специальные действия, создав PictureInPictureParams с помощью PictureInPictureParams.Builder.setActions() перед переходом в режим "Картинка в картинке", и передать параметры при переходе в этот режим с помощью enterPictureInPictureMode(android.app.PictureInPictureParams) или setPictureInPictureParams(android.app.PictureInPictureParams). Будьте осторожны. Если вы попытаетесь добавить больше getMaxNumPictureInPictureActions(), будет добавлено только максимально возможное количество.

Продолжение воспроизведения видео в режиме "Картинка в картинке"

Когда объект activity переключается в режим "картинка в картинке", система приостанавливает его и вызывает метод onPause(). Воспроизведение видео не должно приостанавливаться, а должно продолжаться, если действие приостановлено при переходе в режим "картинка в картинке".

В Android 7.0 (уровень API 24) и более поздних версиях приостанавливать и возобновлять воспроизведение видео следует, когда система вызывает методы onStop() и onStart(). Это позволит вам не проверять, находится ли приложение в режиме "Картинка в картинке" в onPause(), и явно продолжать воспроизведение.

Если вы не установили для флага setAutoEnterEnabled значение true и вам нужно приостановить воспроизведение в реализации onPause(), проверьте, включен ли режим "Картинка в картинке", вызвав isInPictureInPictureMode(), и обработайте воспроизведение соответствующим образом. Пример:

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.
    }
}

Когда объект activity переключается из режима "картинка в картинке" обратно в полноэкранный режим, система возобновляет работу объекта activity и вызывает метод onResume().

Используйте одно действие воспроизведения для режима "Картинка в картинке"

В вашем приложении пользователь может выбрать новое видео на главном экране, пока другое видео воспроизводится в режиме "картинка в картинке". Воспроизводите новое видео в полноэкранном режиме в текущем окне, а не открывайте новое, чтобы не запутать пользователя.

Чтобы для запросов на воспроизведение видео использовалось одно действие, которое при необходимости переключается в режим "Картинка в картинке" и обратно, задайте для атрибута android:launchMode этого действия значение singleTask в манифесте:

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

В действии переопределите onNewIntent() и обработайте новое видео, при необходимости остановив воспроизведение существующего.

Поддержка режима "картинка в картинке" для приложений камеры

Чтобы включить режим "Картинка в картинке" для приложений камеры, убедитесь, что камера остается активной в этом режиме. Для этого не закрывайте ее при вызове onPause():

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

Как и в других случаях, скройте ненужные элементы интерфейса (например, элементы управления и оверлеи) и добавьте настраиваемые действия для управления камерой (например, остановку записи или переключение камеры).

Как рассчитать sourceRectHint для плавных переходов

Чтобы анимация входа была плавной, необходимо указать точные координаты видоискателя камеры в sourceRectHint. Границы можно получить из представления предварительного просмотра с помощью getGlobalVisibleRect(), как показано ниже.

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)

Дополнительные сведения

Режим "Картинка в картинке" может быть недоступен на устройствах с небольшим объемом ОЗУ. Прежде чем использовать режим "Картинка в картинке", убедитесь, что он доступен, вызвав hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

Режим "Картинка в картинке" предназначен для действий, в которых видео воспроизводится в полноэкранном режиме. При переключении в режим "Картинка в картинке" не показывайте ничего, кроме видеоконтента. Отслеживайте, когда ваше действие переходит в режим "картинка в картинке", и скрывайте элементы интерфейса, как описано в разделе Обработка интерфейса в режиме "картинка в картинке".

По умолчанию в режиме "Картинка в картинке" фокус ввода не устанавливается. Чтобы получать события ввода в режиме "картинка в картинке", используйте MediaSession.setCallback(). Подробнее о том, как использовать setCallback(), рассказывается в статье Как показывать карточку "Что сейчас играет?".

Когда приложение находится в режиме PiP, воспроизведение видео в окне PiP может вызвать помехи в звуке другого приложения, например музыкального проигрывателя или приложения голосового поиска. Чтобы избежать этого, запросите фокус звука, когда вы начинаете воспроизведение видео, и обрабатывайте уведомления об изменении фокуса звука, как описано в разделе Управление фокусом звука. Если вы получили уведомление о потере аудиофокуса в режиме PiP, приостановите или остановите воспроизведение видео.

Когда приложение переходит в режим "картинка в картинке", в нем отображается только верхнее действие. В некоторых случаях, например на устройствах с поддержкой многооконного режима, основное действие может быть показано снова рядом с окном "картинка в картинке". Вам нужно обработать этот случай, в том числе обратный вызов onResume() или onPause() для базового действия. Также возможно, что пользователь будет взаимодействовать с действием. Например, если у вас отображается действие со списком видео и воспроизводимое видео в режиме PiP, пользователь может выбрать новое видео из списка, и действие PiP должно соответствующим образом обновиться.

Дополнительные примеры кода

Чтобы скачать пример приложения, написанного на Kotlin, перейдите по ссылке Android PictureInPicture Sample (Kotlin).