Используйте режим «картинка в картинке» (PiP).

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

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

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

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

Функция «картинка в картинке» (PiP) также поддерживается на совместимых устройствах Android TV OS под управлением Android 14 (уровень API 34) или более поздней версии. Несмотря на множество сходств, при использовании PiP на телевизоре следует учитывать дополнительные моменты.

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

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

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

  • Двойное касание окна переключает режимы между текущим размером «картинка в картинке» и максимальным или минимальным размером «картинка в картинке» — например, двойное касание развернутого окна сворачивает его, и обратное также верно.

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

  • Изменяйте размер окна «картинка в картинке» с помощью масштабирования жестом «щипок».

Ваше приложение управляет моментом перехода текущего действия в режим «картинка в картинке». Вот несколько примеров:

  • Приложение может перейти в режим «картинка в картинке», когда пользователь нажимает кнопку «Домой» или проводит пальцем вверх, чтобы перейти на главный экран. Именно так Google Maps продолжает отображать маршрут, пока пользователь одновременно выполняет другое приложение.

  • Ваше приложение может переводить видео в режим «картинка в картинке», когда пользователь возвращается к просмотру другого контента после просмотра видео.

  • Ваше приложение может переключать видео в режим «картинка в картинке», пока пользователь досматривает конец эпизода. На главном экране отображается рекламная информация или краткое описание следующего эпизода сериала.

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

Объявить о поддержке режима «картинка в картинке»

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

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

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

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

Переключитесь в режим «картинка в картинке» (PiP).

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

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

Котлин

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

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

Котлин

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

Java

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

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

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

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

Эти изменения касаются следующего.

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

В качестве примера для создания качественного перехода между изображениями можно использовать Android Kotlin PictureInPicture .

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

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

Для внесения изменений выполните следующие шаги и обратитесь к этому примеру в качестве参考:

  1. Используйте setAutoEnterEnabled для создания объекта PictureInPictureParams.Builder :

    Котлин

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

    Java

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
  2. Вызовите метод setPictureInPictureParams с актуальными значениями PictureInPictureParams на раннем этапе. Приложение не будет ждать обратного вызова onUserLeaveHint (как это было бы в Android 11).

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

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

Установите соответствующее значение sourceRectHint для входа и выхода из режима «картинка в картинке».

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

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

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

  1. Создайте объект PictureInPictureParams используя соответствующие границы в качестве sourceRectHint . Мы также рекомендуем добавить обработчик изменения макета к видеоплееру:

    Котлин

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

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

Отключить плавное изменение размера для контента, не являющегося видео.

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

Для обеспечения плавного изменения размера видеоконтента:

Котлин

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

Java

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

Обработка пользовательского интерфейса во время режима «картинка в картинке»

Когда активность переходит в режим «картинка в картинке» (PiP) или выходит из него, система вызывает 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.
        }
    }

Java

@Override
public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Котлин

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

Java

@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

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

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

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

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

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

Если в приложении активна медиасессия , то появятся элементы управления воспроизведением, паузой, следующим и предыдущим треками.

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

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

Когда ваше приложение переключается в режим «картинка в картинке», система переводит приложение в состояние паузы и вызывает метод onPause() приложения. Воспроизведение видео не должно приостанавливаться, а должно продолжаться, если приложение приостановлено во время перехода в режим «картинка в картинке».

В Android 7.0 и более поздних версиях следует приостанавливать и возобновлять воспроизведение видео, когда система вызывает методы onStop() и onStart() вашего приложения. Таким образом, вы сможете избежать проверки режима «картинка в картинке» в onPause() и необходимости явного продолжения воспроизведения.

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

Котлин

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

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

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

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

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

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

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

Передовые методы

Функция «картинка в картинке» может быть отключена на устройствах с малым объемом оперативной памяти. Прежде чем ваше приложение начнет использовать функцию «картинка в картинке», убедитесь в ее доступности, вызвав метод hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) .

Режим «картинка в картинке» (PiP) предназначен для действий, воспроизводящих видео в полноэкранном режиме. При переключении действия в режим PiP избегайте отображения чего-либо, кроме видеоконтента. Отслеживайте момент перехода действия в режим PiP и скрывайте элементы пользовательского интерфейса, как описано в разделе «Обработка пользовательского интерфейса в режиме PiP» .

Когда активность находится в режиме «картинка в картинке», по умолчанию она не получает фокус ввода. Чтобы получать события ввода в режиме «картинка в картинке», используйте MediaSession.setCallback() . Дополнительную информацию об использовании setCallback() см. в разделе «Отображение карточки «Сейчас воспроизводится»» .

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

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

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

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