WindowManager — это недавнее дополнение к Android Jetpack, которое призвано помочь разработчикам приложений поддерживать новые форм-факторы устройств и обеспечить общее API для различных функций Window Manager как в старых, так и в новых версиях платформы. Первоначальная версия предназначена для складных устройств, а будущие версии будут расширены для поддержки большего количества типов дисплеев и оконных функций.
А зачем мне это нужно?
На рынке появляются новые складные устройства, которые обеспечивают набор уникальных аппаратных функций. Оптимизация приложения для этих новых устройств и форм-факторов позволяет создать различныйопыт и позволит пользователям в полной мере использовать преимущества любого устройства, которое они используют.
Например, Samsung Samsung Galaxy Z Flip — это устройство типа раскладушки, которое поддерживает как сложенное, так и частично сложенное состояние (называемое Samsung Flex Mode). Приложение может оптимизировать свой макет, когда устройство находится в частично сложенном состоянии, и отделить его элементы управления снизу от остального содержимого сверху.
Пользователи могут установить телефон плоско, например, на столе, и использовать нижнюю половину экрана для навигации и взаимодействия с приложением.
Цель библиотеки Jetpack WindowManager — обеспечить единое API для различных типов складных устройств, поступающих на рынок, чтобы разработчики приложений могли ориентироваться на целые категории устройств, а не на одну модель.
В версии 1.0.0 библиотека предоставляет информацию о двух физических свойствах складных устройств — особенностях отображения и состоянии устройства.
Характеристики дисплея
Одна панель дисплея или конфигурация с несколькими дисплеями могут иметь различные функции, которые создадут сбои в непрерывной поверхности экрана. Примерами этого могут быть складки, петли, изогнутые участки или вырезы. Если в окне приложения происходят такие сбои, то компоновку и размещение содержимого в окне можно адаптировать таким образом, чтобы избежать таких областей или воспользоваться ими в качестве естественного разделителя.
Каждая область отображения объектов может быть охарактеризована своим ограничивающим прямоугольником в координатном пространстве окна и его типом. Прямоугольник будет указывать на физические границы объекта. Тип объекта поможет определить, как к нему относиться. Например, некоторые объекты могут создавать физические разделения и/или неинтерактивные области (например, шарнир между двумя индикаторными панелями, вырез дисплея), в то время как другие могут служить логическими разделителями (например, складка).
public class DisplayFeature {
private Rect mRect;
private @Type int mType;
...
}
Первая версия библиотеки поддержки включает в себя только два типа функций: TYPE_FOLD
и TYPE_HINGE
.
@IntDef({
// Складка на экране без физического разрыва.
TYPE_FOLD,
// Физичское разделение шарниром, которое позволяет двум дисплеям складываться.
TYPE_HINGE,
})
public @interface Type{}
Для специально ограничивающего прямоугольника TYPE_FOLD
, как ожидается, будет нулевая высота (0, y, ширина, y) или нулевая ширина(х, 0, х, высота). Это указывает на то, что недоступной области нет, но она все равно сообщает положение на экране.
Состояния устройства
В зависимости от конструкции шарнирной фурнитуры различные складные устройства могут иметь несколько промежуточных состояний: закрытое, частично открытое, полностью открытое (плоская поверхность) или перевернутое. Используя библиотеку, приложения могут предоставлять различные функции в зависимости от этих состояний устройства. Эти состояния определяются как различные позиции:
@IntDef({
POSTURE_UNKNOWN,
POSTURE_CLOSED,
POSTURE_HALF_OPENED,
POSTURE_OPENED,
POSTURE_FLIPPED
})
public @interface Posture{}
Каждое устройство может сообщать о любом подмножестве позиций, определенных выше, в зависимости от их аппаратного обеспечения и желаемого UX.
Как мне это использовать?
Чтобы добавить зависимость от Window Manager, необходимо добавить репозиторий Google Maven в свой проект.
Добавьте зависимости для артефактов, которые вам нужны в build.gradle
для приложения или модуля:
dependencies {
implementation "androidx.window:window:1.0.0-alpha01"
}
Допустим, вы хотите разделять пользовательский интерфейс приложения, например Google Duo, на складных устройствах. Как правило, это имеет смысл делать в тех случаях, когда физическая конфигурация и состояние устройства создают логическое разделение для пользователя. Например, Galaxy Z Flip создает такое логическое разделение, когда он находится в режиме “Flex” или “наполовину сложенном”. Поэтому вам необходимо знать положение сгиба в окне приложения и состояние устройства.
Во-первых, получите экземпляр androidx.window.WindowManager
из Activity:
var windowManager = WindowManager(this /* context */, null /* windowBackend */)
Обратите внимание на параметры.
Во-вторых, контекст используется для инициализации и подключения экземпляра WindowManager
к визуальному объекту на экране. Таким образом, это должен быть визуальный Context
, который означает или Activity
, или ContextWrapper
вокруг него.
В-третьих, серверная часть окна является поставщиком информации для библиотеки поддержки. Передача null
здесь будет означать, что будет использоваться информация об устройстве по умолчанию, и библиотека будет сообщать об отсутствии функций отображения и неизвестном состоянии устройства, когда приложение запущено на обычном телефоне. Однако вы также можете передать пользовательскую реализацию androidx.window.WindowBackend
для эмуляции любого вида складного устройства без доступа к физическому оборудованию (ознакомьтесь с нашим примером приложения).
Поскольку разделение пользовательского интерфейса имеет смысл только тогда, когда есть логическое аппаратное разделение, существует два варианта этой реализации:
- Тип функции отображения —
TYPE_HINGE
, и всегда существует физическое разделение в окне действия. - Тип функции отображения —
TYPE_FOLD
, а состояние экрана — неплоское (то есть неPOSTURE_OPENED
). Для Galaxy Z Flip в частности, нас будет интересоватьPOSTURE_HALF_OPENED
.
Для сгиба нам нужно будет узнать об изменениях положения устройства, поэтому нужно зарегистрировать прослушиватель изменений DeviceState
:
windowManager.registerDeviceStateChangeCallback(
mainThreadExecutor /* Выполняющий */,
callback /* Потребляющий<DeviceState> */)
В обратном вызове вы можете изменить состояние устройства и обновить пользовательский интерфейс, если это необходимо.
Наконец, нам нужно будет получить фактические функции отображения, которые находятся в окне активности.
val displayFeatures = windowManager.windowLayoutInfo.displayFeatures
В зависимости от того, как ваше действие обрабатывает (или не обрабатывает) изменения конфигурации, вам может потребоваться задать его в разных точках жизненного цикла действия. Запросив его в макете для представления декора окна, вы убедитесь, что он всегда актуален после изменения состояния активности:
window.decorView.doOnLayout {
val displayFeatures =
windowManager.windowLayoutInfo.displayFeatures
...
}
Обратите внимание, что положения отображаемых объектов вычисляются относительно координатного пространства окна активности. Поэтому он может быть предоставлен только после того, как действие будет прикреплено к окну. Запрос информации перед этим вызовет исключение.
Настоятельно рекомендуем изучить демонстрационное приложение для библиотеки. Оно также содержит примеры вычисления положения объектов отображения в иерархии представлений и пример макета, который автоматически разбивает содержимое, если обнаруживается разделяющий объект отображения.
Читайте также:
- Области видимости в Android и Hilt
- Внедрение зависимостей на Android с помощью Hilt
- Как настроить базу данных с Firebase Firestore для Android
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Kenneth Ford: Support New Form Factors with the new Jetpack WindowManager Library