Как разработать Picture-in-Picture режим на Android? С примерами кода — обложка

В последние годы смартфоны всё больше приближаются по возможностям к компьютерам и для многих уже стали основным инструментом работы. Раньше персональные компьютеры имели преимущество в виде поддержки нескольких окон одновременно — такой функции на смартфонах не было. Однако с выходом Android 7.0 ситуация изменилась: появилась поддержка многооконного режима.

Удобство небольшого плавающего окна с видео собеседника при свёрнутом звонке сложно переоценить — можно продолжать диалог и параллельно делать записи или уточнять информацию. В Android есть два способа реализовать такую функциональность: работа приложения в плавающем окне и режим «картинка в картинке» (picture-in-picture). В идеале приложение должно поддерживать оба варианта, но плавающее окно сложнее в разработке и накладывает определённые ограничения на общий дизайн приложения. Поэтому рассмотрим именно режим «картинка в картинке» (далее — PIP) как относительно простой способ добавить поддержку многооконности в своё приложение.

PIP режим для видеозвонков на Android
PIP режим для видеозвонков на Android

Переход в режим PIP

Режим PIP поддерживается на большинстве устройств с Android 8 и выше. Соответственно, если вы поддерживаете версии системы ниже этой, все вызовы, связанные с режимом PIP, должны быть обернуты в проверку версии системы:

 
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Что-то связанное с PIP
}
 

В PIP переводится вся `Activity` целиком, и для начала нужно объявить поддержку PIP и указать, что `Activity` обрабатывает изменения конфигурации в `AndroidManifest.xml`:

 
 activity
…
android:configChanges="screenSize|smallestScreenSize|screenLayout|
orientation"
android:supportsPictureInPicture="true" /
 

Перед использованием режима picture-in-picture нужно убедиться, что устройство пользователя его поддерживает. Для этого воспользуемся `PackageManager`

 
 val isPipSupported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
 

После этого в простейшем виде переход в режим picture-in-picture делается одной строкой кода:

 
 this.enterPictureInPictureMode()
 

Но чтобы перейти в него, нужно понимать, когда это удобно пользователю. Можно добавить отдельную кнопку — переход будет происходить по нажатию. Чаще всего используется автоматический переход: он срабатывает, когда пользователь сворачивает приложение во время звонка — например, нажав кнопку Home или Recent.

Начиная с Android 12 это можно реализовать, установив PictureInPictureParams с флагом setAutoEnterEnabled в Activity:

 
 val pipParams = PictureInPictureParams.Builder()
.setAutoEnterEnabled(true)
.build()
setPictureInPictureParams(pipParams)
 

На устройствах с Android 11 или ниже Activity должна явно вызывать enterPictureInPictureMode() в Activity.onUserLeaveHint:

 
 override fun onUserLeaveHint() {
...
if (isPipSupported && imaginaryCallManager.isInCall)

this.enterPictureInPictureMode()
}
 

Адаптация интерфейса

Отлично, теперь наш экран звонка автоматически переходит в режим PIP! Но в этом режиме часто остаются кнопки «закончить звонок» или «сменить камеру», которые не работают. Их лучше скрыть при переходе.

Для отслеживания перехода в или из режима PIP в `Activity` и `Fragment` используется метод `onPictureInPictureModeChanged`. Переопределим его и скроем ненужные элементы интерфейса:

 
 override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration?
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
setIsUiVisible(isInPictureInPictureMode)
}
 

Окно PIP очень маленькое, поэтому имеет смысл скрыть всё, кроме видео собеседника, включая своё собственное — оно всё равно будет слишком маленьким, чтобы что-то разглядеть.

Как разработать PiP для приложения на Android

Кастомизация

Окно PIP можно дополнительно настроить, передав `PictureInPictureParams` в вызов `enterPictureInPictureMode`. Возможностей настройки немного, но стоит обратить внимание на возможность добавить кнопки в нижнюю часть окна. Это удобно — экран остаётся интерактивным, даже если обычные кнопки перестают работать в режиме PIP.

Максимальное количество кнопок зависит от разных факторов, но минимум три всегда можно добавить. Кнопки, превышающие лимит, просто не отобразятся, поэтому особенно важные лучше размещать в начале. Точный лимит в текущей конфигурации можно узнать с помощью метода `Activity`:

 
 this.maxNumPictureInPictureActions
 

Давайте добавим в наше окно PIP кнопку для завершения звонка. Как и с уведомлениями, нам понадобится `PendingIntent`, который будет сообщать приложению, что кнопка была нажата. Если вы впервые слышали про `PendingIntent`.

После этого можно приступить к созданию описания кнопки, а именно — `RemoteAction`

 
 val endCallPendingIntent = getPendingIntent()
val endCallAction = RemoteAction(
// Иконка для кнопки; цвет будет проигнорирован и заменен на системный
Icon.createWithResource(this, R.drawable.ic_baseline_call_end_24),
// Текст кнопки, который не будет показан
"End call",
// ContentDescription для screen readers
"End call button",
// Наш PendingIntent, который будет запущен при нажатии на кнопку
endCallPendingIntent
)
 

Наше "действие" готово, теперь нужно добавить его в параметры PIP и, соответственно, в вызов перехода в режим

Для начала создадим Builder наших параметров кастомизации:

 
val pipParams = PictureInPictureParams.Builder()
.setActions(listOf(endCallAction))
.build()
this.enterPictureInPictureMode(pipParams)
 
Как настроить режим «картинка в картинке» в Android-приложении

Начиная с Android 8 можно определить область экрана, которая будет отображаться при переходе в PIP. Это можно сделать используя метод setSourceRectHint:

Помимо кнопок, через параметры можно задать соотношение сторон окна PIP или настройки анимации перехода в этот режим.

Итог

Мы рассмотрели простой, но очень удобный способ использовать многооконность системы для улучшения пользовательского опыта: научились добавлять к окну PIP кнопки и адаптировать интерфейс при входе в этот режим и выходе из него.

  • Разработка