Поддержка окон рабочего стола

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

На рисунке 1 показана организация экрана с включённым режимом окон рабочего стола. Обратите внимание:

  • Пользователи могут запускать несколько приложений одновременно.
  • Панель задач расположена в фиксированном положении в нижней части экрана, отображая запущенные приложения. Пользователи могут закреплять приложения для быстрого доступа.
  • Новая настраиваемая панель заголовка украшает верхнюю часть каждого окна такими элементами управления, как свернуть и развернуть.
Дисплей планшета, на котором показано несколько приложений, работающих в окнах изменяемого размера с панелью задач внизу.
Рисунок 1. Окно рабочего стола на планшете.

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

Когда приложение открыто в окне рабочего стола, другие приложения также открываются в окнах рабочего стола.

Рисунок 2. Нажмите, удерживайте и перетащите маркер окна приложения, чтобы войти в режим управления окнами рабочего стола.

Пользователи также могут вызывать оконный режим рабочего стола из меню, которое отображается под дескриптором окна при нажатии или щелчке по дескриптору или при использовании сочетания клавиш Meta (Windows, Command или Search) + Ctrl + Down .

Чтобы выйти из режима окон рабочего стола, нужно закрыть все активные окна или перетащить приложение за маркер окна в верхней части экрана. Сочетание клавиш Meta + H также позволяет выйти из режима окон рабочего стола и снова запустить приложение в полноэкранном режиме.

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

Режим изменения размера и совместимости

В оконном режиме рабочего стола приложения с заблокированной ориентацией могут свободно изменять размер. Это означает, что даже если действие заблокировано в портретной ориентации , пользователи всё равно могут изменить размер окна приложения на альбомную ориентацию.

Рисунок 3. Изменение размера окна приложения с портретной ориентацией на альбомную.

Пользовательский интерфейс приложений, объявленных как неизменяемые (то есть resizeableActivity = false ), масштабируется с сохранением того же соотношения сторон.

Рисунок 4. Пользовательский интерфейс приложения, размер которого не поддается изменению, масштабируется при изменении размера окна.

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

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

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

Рисунок 5. Видоискатель камеры сохраняет соотношение сторон при изменении размера окна.

Настраиваемые вставки заголовков

Все приложения, работающие в оконном режиме рабочего стола, имеют панель заголовка, даже в режиме погружения . Убедитесь, что содержимое приложения не перекрыто панелью заголовка. Панель заголовка представляет собой вложенный тип панели: WindowInsets.Companion.captionBar() ; в представлениях — WindowInsets.Type.captionBar() , который является частью системных панелей.

Дополнительную информацию об обработке вставок можно найти в статье Отображение содержимого от края до края в вашем приложении и обработка вставок окон в Compose .

Панель заголовка также можно настраивать. В Android 15 появился тип оформления APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND который делает панель заголовка прозрачной и позволяет приложениям отображать внутри неё пользовательский контент.

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

Приложения могут переключать внешний вид системных элементов внутри заголовка для светлой и темной тем с помощью APPEARANCE_LIGHT_CAPTION_BARS , аналогично тому, как переключаются строка состояния и панель навигации.

В Android 15 также появился метод WindowInsets#getBoundingRects() , который позволяет приложениям более детально анализировать вставки заголовка. Приложения могут различать области, где система отображает системные элементы, и неиспользуемые области, где приложения могут размещать пользовательский контент, не перекрывая системные элементы.

Список объектов Rect , возвращаемых API, указывает области системы, которых следует избегать. Оставшееся пространство (рассчитываемое путём вычитания прямоугольников из значений Insets в строке заголовка) — это область, где приложение может рисовать, не перекрывая системные элементы и сохраняя возможность получать входные данные.

Chrome до и после внедрения пользовательских заголовков.
Рисунок 6. Chrome до и после внедрения пользовательских заголовков.

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

// In a custom View's onLayout or a similar lifecycle method
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)
    if (changed) {
        // Calculate the height of your custom header
        val customHeaderHeight = 100 // Replace with your actual header height in pixels

        // Create a Rect covering your custom header area
        val exclusionRect = Rect(0, 0, width, customHeaderHeight)

        // Set the exclusion rects for the system
        systemGestureExclusionRects = listOf(exclusionRect)
    }
}

Многозадачность и поддержка нескольких экземпляров

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

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

Вы можете объявить PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI в AndroidManifest.xml вашего приложения в теге <activity> :

<activity
    android:name=".MyActivity"
    android:exported="true"
    android:resizeableActivity="true">
    <meta-data
        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
        android:value="true" />
</activity>

Управляйте экземплярами приложений с помощью жестов перетаскивания

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

Рисунок 7. Запустите новый экземпляр Chrome, перетащив вкладку за пределы окна рабочего стола.

В Android 15 представлены два флага для настройки поведения перетаскивания:

  • DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG : Указывает, что необработанное перетаскивание должно быть делегировано системе для запуска, если ни одно видимое окно не обрабатывает это перетаскивание. При использовании этого флага вызывающий объект должен предоставить ClipData объект ClipData.Item , содержащий неизменяемый IntentSender для запускаемой активности (см. ClipData.Item.Builder#setIntentSender() ). Система может запустить намерение или нет в зависимости от таких факторов, как текущий размер экрана или режим окна. Если система не запускает намерение, оно отменяется посредством обычного процесса перетаскивания.

  • DRAG_FLAG_GLOBAL_SAME_APPLICATION : Указывает, что операция перетаскивания может пересекать границы окна (для нескольких экземпляров одного и того же приложения).

    При вызове [ startDragAndDrop() ][20] с установленным этим флагом только видимые окна, принадлежащие одному и тому же приложению, могут участвовать в операции перетаскивания и получать перетаскиваемое содержимое.

В следующем примере показано, как использовать эти флаги с startDragAndDrop() :

// Assuming 'view' is the View that initiates the drag
view.setOnLongClickListener {
    // Create an IntentSender for the activity you want to launch
    val launchIntent = Intent(view.context, NewInstanceActivity::class.java)
    val pendingIntent = PendingIntent.getActivity(
        view.context,
        0,
        launchIntent,
        PendingIntent.FLAG_IMMUTABLE // Ensure the PendingIntent is immutable
    )

    // Build the ClipData.Item with the IntentSender
    val item = ClipData.Item.Builder()
        .setIntentSender(pendingIntent.intentSender)
        .build()

    // Create ClipData with a simple description and the item
    val dragData = ClipData(
        ClipDescription("New Instance Drag", arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)),
        item
    )

    // Combine the drag flags
    val dragFlags = View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG or
                    View.DRAG_FLAG_GLOBAL_SAME_APPLICATION

    // Start the drag operation
    view.startDragAndDrop(
        dragData,                     // The ClipData to drag
        View.DragShadowBuilder(view), // A visual representation of the dragged item
        null,                         // Local state object (not used here)
        dragFlags                     // The drag flags
    )
    true // Indicate that the long click was consumed
}
Рисунок 8. Перемещение вкладки между двумя экземплярами приложения Chrome.

Дополнительные оптимизации

Настройте запуск приложений и переводите приложения из оконного режима рабочего стола в полноэкранный режим.

Укажите размер и положение по умолчанию

Не всем приложениям, даже с возможностью изменения размера, требуется большое окно для предоставления пользователю полезных функций. Вы можете использовать метод ActivityOptions#setLaunchBounds() чтобы задать размер и положение по умолчанию при запуске активности.

Вот пример того, как установить границы запуска для активности:

val options = ActivityOptions.makeBasic()

// Define the desired launch bounds (left, top, right, bottom in pixels)
val launchBounds = Rect(100, 100, 700, 600) // Example: 600x500 window at (100,100)

// Apply the launch bounds to the ActivityOptions
options.setLaunchBounds(launchBounds)

// Start the activity with the specified options
val intent = Intent(this, MyActivity::class.java)
startActivity(intent, options.toBundle())

Перейти в полноэкранный режим с рабочего стола

Приложения могут переходить в полноэкранный режим с помощью вызова Activity#requestFullScreenMode() . Этот метод отображает приложение на весь экран непосредственно из окон рабочего стола.

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

// In an Activity
fun enterFullScreen() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 15 (U)
        requestFullScreenMode()
    }
}