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

Удобство небольшого плавающего окна с видео собеседника при свёрнутом звонке сложно переоценить -- можно продолжать диалог и параллельно вести записи или уточнять какую-то информацию. В Android есть два варианта реализации такой функциональности: поддержка работы приложения в плавающем окне и режим "picture-in-picture". В идеале приложение должно поддерживать оба подхода, но плавающее окно сложнее в разработке и накладывает определённые ограничения на дизайн приложения в целом, поэтому рассмотрим именно 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 или ниже Activit` должна явно вызывать 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)
 
Как кастомизировать picture-in-picture режим в Android приложении

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

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

Итог

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

  • Разработка