Встраивание Activity оптимизирует приложения для устройств с большим экраном, разделяя окно задачи приложения между двумя Activity или двумя экземплярами одного и того же Activity.
Если ваше приложение состоит из нескольких действий, встраивание действий позволяет улучшить взаимодействие с пользователем на планшетах, складных устройствах и устройствах с ChromeOS.
Встраивание действий не требует рефакторинга кода. Вы можете определить, как будут отображаться объекты activity в вашем приложении – рядом или друг над другом, – создав XML-файл конфигурации или выполнив вызовы API Jetpack WindowManager.
Поддержка небольших экранов выполняется автоматически. Если приложение запущено на устройстве с небольшим экраном, действия будут располагаться одно над другим. На больших экранах действия отображаются рядом друг с другом. Система определяет презентацию на основе созданной вами конфигурации. Логика ветвления не требуется.
Встраивание действий поддерживает изменение ориентации устройства и без проблем работает на складных устройствах, складывая и раскладывая действия при складывании и раскладывании устройства.
Встраивание действий поддерживается на большинстве устройств с большим экраном, на которых установлена ОС Android 12L (уровень API 32) или более поздней версии.
Как разделить окно задачи
При встраивании действий окно задачи приложения разделяется на два контейнера: основной и дополнительный. Контейнеры содержат активности, запущенные из основной активности или из других активностей, уже находящихся в контейнерах.
При запуске действия добавляются в дополнительный контейнер, который на небольших экранах располагается поверх основного. Таким образом, стек объектов activity и обратная навигация соответствуют тому, как они реализованы в вашем приложении.
Встраивание действий позволяет показывать их разными способами. Приложение может разделить окно задачи, запустив два действия одновременно рядом или одно над другим:
Объект Activity, занимающий все окно задачи, может создать разделение, запустив новый объект Activity рядом:
Действия, которые уже выполняются в режиме разделенного экрана и используют одно окно задач, могут запускать другие действия следующими способами:
Сбоку над другим действием:
Рисунок 4. Действие А запускает действие С сбоку над действием Б. Сбоку, и сдвинуть разделение в сторону, скрыв предыдущее основное действие:
Рисунок 5. Действие Б запускает действие В сбоку и сдвигает разделение в сторону. Запустить действие поверх текущего, то есть в том же стеке объектов activity:
Рисунок 6. Действие B запускает действие C без дополнительных флагов намерения. Запустить действие в полноэкранном режиме в той же задаче:
Рисунок 7. Действие А или Б запускает действие В, которое занимает окно задачи.
Обратная навигация
В зависимости от зависимостей между Activity или способа запуска события "Назад" в приложениях разных типов могут действовать разные правила обратной навигации в режиме разделенного окна задач, например:
- Если действия связаны и одно не должно показываться без другого, обратную навигацию можно настроить так, чтобы завершались оба действия.
- Если действия полностью независимы, обратная навигация в одном из них не влияет на состояние другого действия в окне задачи.
Событие "Назад" отправляется в последнее активное окно при использовании навигации с помощью кнопок.
Для навигации с помощью жестов:
Android 14 (уровень API 34) и более ранние версии. Событие "Назад" отправляется в действие, в котором был выполнен жест. Когда пользователь проводит пальцем по экрану слева, событие "Назад" отправляется в действие в левой части разделенного окна. Когда пользователь проводит пальцем от правого края экрана, событие "Назад" отправляется в действие на правой панели.
Android 15 (уровень API 35) и более поздние версии
При работе с несколькими действиями в одном приложении жест завершает верхнее действие независимо от направления прокрутки, что обеспечивает более единообразный опыт.
Если на экране одновременно показываются два приложения (одно поверх другого), событие "Назад" направляется в последнее активное приложение, как и при использовании кнопок навигации.
Макет с несколькими панелями
Jetpack WindowManager позволяет создавать многопанельные макеты с внедрением Activity на устройствах с большим экраном, на которых установлена ОС Android 12L (API уровня 32) или более поздняя версия, а также на некоторых устройствах с более ранними версиями платформы. Существующие приложения, основанные на нескольких действиях, а не на фрагментах или макетах на основе представлений, например SlidingPaneLayout, могут обеспечить улучшенное взаимодействие с пользователем на большом экране без рефакторинга исходного кода.
Один из распространенных примеров – разделение на список и подробные сведения. Чтобы обеспечить высокое качество представления, система запускает активность списка, а затем приложение сразу же запускает активность с подробной информацией. Система перехода ждет, пока будут отрисованы оба действия, а затем показывает их вместе. Для пользователя эти два действия будут выглядеть как одно.
Разделение атрибутов
Вы можете указать, как окно задачи будет разделено между контейнерами и как они будут расположены относительно друг друга.
Для правил, заданных в файле конфигурации XML, укажите следующие атрибуты:
splitRatio– задает пропорции контейнера. Значение представляет собой число с плавающей запятой в открытом интервале (0,0, 1,0).splitLayoutDirection– определяет, как разделенные контейнеры располагаются относительно друг друга. Возможны следующие значения:ltr: слева направоrtl: справа налевоlocale:ltrилиrtlопределяется на основе региональных настроек.
Примеры приведены в разделе Конфигурация XML.
Для правил, созданных с помощью API WindowManager, создайте объект SplitAttributes с SplitAttributes.Builder и вызовите следующие методы построителя:
setSplitType()– задает пропорции разделенных контейнеров. Допустимые аргументы, в том числе методSplitAttributes.SplitType.ratio(), описаны в разделеSplitAttributes.SplitType.setLayoutDirection()– задает макет контейнеров. Возможные значения:SplitAttributes.LayoutDirection.
Примеры можно найти в разделе WindowManager API.
Разделение ориентации
Размеры и соотношение сторон экрана определяют расположение действий в разделенных представлениях. На больших экранах в альбомной ориентации действия отображаются рядом друг с другом, а на экранах в книжной ориентации или в режиме планшета на складных устройствах – одно над другим.
Ориентацию разделения можно задать с помощью калькулятора SplitController
SplitAttributes. Калькулятор рассчитывает SplitAttributes для активного SplitRule.
Используйте калькулятор, чтобы разделить родительский контейнер в разных направлениях для разных состояний устройства, например:
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator { params -> val parentConfiguration = params.parentConfiguration val builder = SplitAttributes.Builder() return@setSplitAttributesCalculator if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build() } else if (parentConfiguration.screenHeightDp >= 600) { // Horizontal split for tall displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) .build() } else { // Fallback to expand the secondary container. builder .setSplitType(SPLIT_TYPE_EXPAND) .build() } } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator(params -> { Configuration parentConfiguration = params.getParentConfiguration(); SplitAttributes.Builder builder = new SplitAttributes.Builder(); if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build(); } else if (parentConfiguration.screenHeightDp >= 600) { // Horizontal split for tall displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) .build(); } else { // Fallback to expand the secondary container. return builder .setSplitType(SplitType.SPLIT_TYPE_EXPAND) .build(); } }); }
На складных устройствах можно разделить экран по вертикали, если устройство находится в альбомной ориентации, отобразить одно действие, если устройство находится в портретной ориентации, и разделить экран по горизонтали, если устройство находится в настольном положении:
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator { params -> val tag = params.splitRuleTag val parentWindowMetrics = params.parentWindowMetrics val parentConfiguration = params.parentConfiguration val foldingFeatures = params.parentWindowLayoutInfo.displayFeatures.filterIsInstance<FoldingFeature>() val feature = if (foldingFeatures.size == 1) foldingFeatures[0] else null val builder = SplitAttributes.Builder() builder.setSplitType(SPLIT_TYPE_HINGE) return@setSplitAttributesCalculator if (feature?.isSeparating == true) { // Horizontal split for tabletop posture. builder .setSplitType(SPLIT_TYPE_HINGE) .setLayoutDirection( if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL) { SplitAttributes.LayoutDirection.BOTTOM_TO_TOP } else { SplitAttributes.LayoutDirection.LOCALE } ) .build() } else if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build() } else { // No split for tall displays. builder .setSplitType(SPLIT_TYPE_EXPAND) .build() } } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator(params -> { String tag = params.getSplitRuleTag(); WindowMetrics parentWindowMetrics = params.getParentWindowMetrics(); Configuration parentConfiguration = params.getParentConfiguration(); List<FoldingFeature> foldingFeatures = params.getParentWindowLayoutInfo().getDisplayFeatures().stream().filter( item -> item instanceof FoldingFeature) .map(item -> (FoldingFeature) item) .collect(Collectors.toList()); FoldingFeature feature = foldingFeatures.size() == 1 ? foldingFeatures.get(0) : null; SplitAttributes.Builder builder = new SplitAttributes.Builder(); builder.setSplitType(SplitType.SPLIT_TYPE_HINGE); if (feature != null && feature.isSeparating()) { // Horizontal slit for tabletop posture. return builder .setSplitType(SplitType.SPLIT_TYPE_HINGE) .setLayoutDirection( feature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL ? SplitAttributes.LayoutDirection.BOTTOM_TO_TOP : SplitAttributes.LayoutDirection.LOCALE) .build(); } else if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build(); } else { // No split for tall displays. return builder .setSplitType(SplitType.SPLIT_TYPE_EXPAND) .build(); } }); }
Теги
Действия с плейсхолдером – это пустые дополнительные действия, которые занимают область разделения активности. В конечном итоге их нужно заменить на другое действие, содержащее контент. Например, в макете "список и подробные сведения" второстепенная сторона разделенного экрана может быть занята заглушкой, пока пользователь не выберет элемент из списка. После этого заглушка будет заменена экраном с подробной информацией о выбранном элементе.
По умолчанию система показывает плейсхолдеры, только если для разделения действий достаточно места. Когда размер экрана становится слишком мал для показа разделенного экрана, плейсхолдеры автоматически закрываются. Если позволяет место, система перезапускает плейсхолдер с инициализированным состоянием.
Однако атрибут stickyPlaceholder метода SplitPlaceholderRule или setSticky() объекта SplitPlaceholder.Builder может переопределить поведение по умолчанию. Если атрибут или метод задает значение true, система отображает заполнитель как самое верхнее действие в окне задач, когда размер экрана уменьшается с двухпанельного до однопанельного (пример можно найти в разделе Разделение экрана).
Изменение размера окна
Если при изменении конфигурации устройства ширина окна задачи уменьшается настолько, что оно становится слишком узким для многопанельного макета (например, когда большой складной телефон складывается из планшета в телефон или когда размер окна приложения изменяется в многооконном режиме), действия, не являющиеся заполнителями, во вторичной панели окна задачи располагаются поверх действий в основной панели.
Заполнители показываются, только если на экране достаточно места для разделения. На небольших экранах заставка автоматически закрывается. Когда область отображения снова становится достаточно большой, плейсхолдер создается заново. (см. раздел Плейсхолдеры).
Возможность наложения окон обеспечивается тем, что WindowManager располагает окна на дополнительной панели поверх окон на основной панели.
Несколько действий на вспомогательной панели
Действие B запускает действие C без дополнительных флагов намерения:

В результате порядок задач в стеке будет следующим:

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

При возврате назад в маленьком окне вы переходите к предыдущим действиям, которые были выполнены в приложении.
Если окно задачи будет восстановлено до большего размера, позволяющего разместить несколько панелей, действия снова будут отображаться рядом друг с другом.
Многоуровневые сплиты
Действие Б запускает действие В сбоку и сдвигает разделение в сторону:

В результате порядок расположения окон в одной задаче будет следующим:

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

Фиксированная вертикальная ориентация
Параметр манифеста android:screenOrientation позволяет приложениям ограничивать ориентацию экрана (вертикальную или горизонтальную). Чтобы улучшить взаимодействие с пользователем на устройствах с большим экраном, таких как планшеты и складные телефоны, производители устройств могут игнорировать запросы на ориентацию экрана и показывать приложение в режиме Letterbox в портретной ориентации на альбомных дисплеях или в альбомной ориентации на портретных дисплеях.
Аналогичным образом, если встроена функция встраивания действий, производители устройств могут настроить их так, чтобы действия, предназначенные для фиксированной вертикальной ориентации, отображались в горизонтальной ориентации на больших экранах (ширина ≥ 600 dp) с черными полосами по бокам. Если активность с фиксированной портретной ориентацией запускает вторую активность, устройство может отобразить их рядом в двухпанельном режиме.
Всегда добавляйте в файл манифеста приложения свойство android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED, чтобы устройства знали, что приложение поддерживает встраивание действий (см. раздел Разделение конфигурации). На устройствах, настроенных производителем, можно определить, нужно ли добавлять рамки к действиям с фиксированной вертикальной ориентацией.
Конфигурация разделения
Правила разделения настраивают разделение активности. Правила разделения задаются в XML-файле конфигурации или с помощью вызовов API WindowManager из Jetpack.
В любом случае приложению необходимо получить доступ к библиотеке WindowManager и сообщить системе, что в нем реализовано встраивание окон.
Сделайте следующее:
Добавьте в файл
build.gradleна уровне модуля приложения зависимость от последней версии библиотеки WindowManager, например:implementation 'androidx.window:window:1.1.0-beta02'Библиотека WindowManager содержит все компоненты, необходимые для встраивания действий.
Сообщите системе, что в вашем приложении реализовано встраивание действий.
Добавьте свойство
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLEDв элемент <application> файла манифеста приложения и задайте значение true, например:<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <property android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" android:value="true" /> </application> </manifest>В WindowManager версии 1.1.0-alpha06 и более поздних версиях встраивание активности разделяется, если только свойство не добавлено в манифест и не установлено значение true.
Производители устройств также используют этот параметр, чтобы включить специальные возможности для приложений, поддерживающих встраивание действий. Например, устройства могут показывать только вертикальную ориентацию на горизонтальных экранах, чтобы при запуске второго приложения можно было перейти к двухпанельному макету (см. раздел Фиксированная вертикальная ориентация).
Конфигурация XML
Чтобы создать реализацию встраивания действий на основе XML, выполните следующие действия:
Создайте XML-файл ресурсов, который будет:
- Определяет действия, которые делят сплит.
- Настраивает параметры разделения.
- Создает плейсхолдер для дополнительного контейнера разделенного контента, если он недоступен.
- Указывает действия, которые никогда не должны быть частью разделения.
Пример:
<!-- main_split_config.xml --> <resources xmlns:window="http://schemas.android.com/apk/res-auto"> <!-- Define a split for the named activities. --> <SplitPairRule window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:finishPrimaryWithSecondary="never" window:finishSecondaryWithPrimary="always" window:clearTop="false"> <SplitPairFilter window:primaryActivityName=".ListActivity" window:secondaryActivityName=".DetailActivity"/> </SplitPairRule> <!-- Specify a placeholder for the secondary container when content is not available. --> <SplitPlaceholderRule window:placeholderActivityName=".PlaceholderActivity" window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:stickyPlaceholder="false"> <ActivityFilter window:activityName=".ListActivity"/> </SplitPlaceholderRule> <!-- Define activities that should never be part of a split. Note: Takes precedence over other split rules for the activity named in the rule. --> <ActivityRule window:alwaysExpand="true"> <ActivityFilter window:activityName=".ExpandedActivity"/> </ActivityRule> </resources>Создайте инициализатор.
Компонент WindowManager
RuleControllerанализирует XML-файл конфигурации и делает правила доступными для системы. Библиотека Jetpack StartupInitializerделает XML-файл доступным дляRuleControllerпри запуске приложения, чтобы правила действовали при запуске любых действий.Чтобы создать инициализатор, выполните следующие действия:
Добавьте в файл
build.gradleна уровне модуля зависимость от последней версии библиотеки Jetpack Startup, например:implementation 'androidx.startup:startup-runtime:1.1.1'Создайте класс, реализующий интерфейс
Initializer.Инициализатор делает правила разбиения доступными для
RuleController, передавая идентификатор XML-файла конфигурации (main_split_config.xml) методуRuleController.parseRules().Kotlin
class SplitInitializer : Initializer<RuleController> { override fun create(context: Context): RuleController { return RuleController.getInstance(context).apply { setRules(RuleController.parseRules(context, R.xml.main_split_config)) } } override fun dependencies(): List<Class<out Initializer<*>>> { return emptyList() } }
Java
public class SplitInitializer implements Initializer<RuleController> { @NonNull @Override public RuleController create(@NonNull Context context) { RuleController ruleController = RuleController.getInstance(context); ruleController.setRules( RuleController.parseRules(context, R.xml.main_split_config) ); return ruleController; } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Collections.emptyList(); } }
Создайте поставщика контента для определений правил.
Добавьте
androidx.startup.InitializationProviderв файл манифеста приложения в качестве<provider>. Добавьте ссылку на реализацию инициализатораRuleControllerSplitInitializer:<!-- AndroidManifest.xml --> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <!-- Make SplitInitializer discoverable by InitializationProvider. --> <meta-data android:name="${applicationId}.SplitInitializer" android:value="androidx.startup" /> </provider>InitializationProviderобнаруживает и инициализируетSplitInitializerдо того, как вызывается методonCreate()приложения. В результате правила разделения применяются при запуске основного действия приложения.
WindowManager API
Встраивание действий можно реализовать программно с помощью нескольких вызовов API. Выполняйте вызовы в методе onCreate() подкласса Application, чтобы правила действовали до запуска любых действий.
Чтобы создать разделение действий программным способом, выполните следующие действия:
Чтобы создать правило разделения:
Создайте
SplitPairFilter, который идентифицирует действия, связанные с разделением:Kotlin
val splitPairFilter = SplitPairFilter( ComponentName(this, ListActivity::class.java), ComponentName(this, DetailActivity::class.java), null )
Java
SplitPairFilter splitPairFilter = new SplitPairFilter( new ComponentName(this, ListActivity.class), new ComponentName(this, DetailActivity.class), null );
Добавьте фильтр в набор фильтров:
```Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
Создайте атрибуты макета для разделения:
Kotlin
val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build()
Java
SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
SplitAttributes.Builderсоздает объект, содержащий атрибуты макета:setSplitType()определяет, как доступная область экрана распределяется между контейнерами действий. Тип разделения "Пропорция" определяет, какая часть доступной области экрана выделяется для основного контейнера. Второстепенный контейнер занимает оставшуюся часть.setLayoutDirection()– определяет, как контейнеры действий расположены относительно друг друга (первым указывается основной контейнер).
Создайте
SplitPairRule:Kotlin
val splitPairRule = SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build()
Java
SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build();
SplitPairRule.Builderсоздает и настраивает правило:filterSet– содержит фильтры разделенных пар, которые определяют, когда применять правило, идентифицируя действия, которые разделяют разделение.setDefaultSplitAttributes(): применяет атрибуты макета к правилу.setMinWidthDp()– минимальная ширина экрана (в пикселях, не зависящих от плотности экрана), при которой можно разделить экран.setMinSmallestWidthDp()– задает минимальное значение (в dp), которое должна иметь меньшая из двух сторон экрана, чтобы можно было включить разделение независимо от ориентации устройства.setMaxAspectRatioInPortrait()– задает максимальное соотношение сторон экрана (высота:ширина) в портретной ориентации, при котором отображаются разделения активности. Если соотношение сторон вертикального дисплея превышает максимальное, разделение экрана отключается независимо от ширины дисплея. Примечание. Значение по умолчанию – 1, 4. При таком значении действия занимают все окно задачи в портретной ориентации на большинстве планшетов. Дополнительная информация:SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTиsetMaxAspectRatioInLandscape(). Значение по умолчанию для альбомной ориентации –ALWAYS_ALLOW.setFinishPrimaryWithSecondary()– определяет, как завершение всех действий во вспомогательном контейнере влияет на действия в основном контейнере.NEVERозначает, что система не должна завершать основные действия, когда будут выполнены все действия во вторичном контейнере (см. Завершение действий).setFinishSecondaryWithPrimary()– определяет, как завершение всех действий в основном контейнере влияет на действия во вспомогательном контейнере.ALWAYSозначает, что система должна всегда завершать действия во вторичном контейнере, когда все действия в основном контейнере завершены (см. Завершение действий).setClearTop(): указывает, завершаются ли все действия во вспомогательном контейнере при запуске нового действия в контейнере. Значениеfalseуказывает, что новые действия добавляются к действиям, уже находящимся во вспомогательном контейнере.
Получите экземпляр WindowManager
RuleControllerи добавьте правило:Kotlin
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
Java
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
Создайте заполнитель для дополнительного контейнера, если контент недоступен:
Создайте
ActivityFilter, который идентифицирует действие, с которым плейсхолдер разделяет временное окно задачи:Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
Добавьте фильтр в набор фильтров:
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
Создайте
SplitPlaceholderRule:Kotlin
val splitPlaceholderRule = SplitPlaceholderRule.Builder( placeholderActivityFilterSet, Intent(context, PlaceholderActivity::class.java) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build()
Java
SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder( placeholderActivityFilterSet, new Intent(this, PlaceholderActivity.class) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build();
SplitPlaceholderRule.Builderсоздает и настраивает правило:placeholderActivityFilterSet: содержит фильтры действий, которые определяют, когда применять правило, идентифицируя действия, с которыми связано действие-заполнитель.Intent– запуск действия-заполнителя.setDefaultSplitAttributes()– применяет к правилу атрибуты макета.setMinWidthDp(): Задает минимальную ширину экрана (в пикселях, не зависящих от плотности экрана), при которой возможен разделенный экран.setMinSmallestWidthDp(): Устанавливает минимальное значение (в dp), которое должна иметь меньшая из двух сторон экрана, чтобы разрешить разделение независимо от ориентации устройства.setMaxAspectRatioInPortrait(): Устанавливает максимальное соотношение сторон экрана (высота:ширина) в портретной ориентации, при котором отображаются разделения активности. Примечание. Значение по умолчанию – 1, 4. При таком значении окна задач в большинстве планшетов в портретной ориентации заполняются полностью. Дополнительная информация:SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTиsetMaxAspectRatioInLandscape(). Значение по умолчанию для альбомной ориентации –ALWAYS_ALLOW.setFinishPrimaryWithPlaceholder(): Определяет, как завершение действия-заполнителя влияет на действия в основном контейнере. ALWAYS означает, что система всегда должна завершать действия в основном контейнере, когда завершается плейсхолдер (см. раздел Завершение действий).setSticky(): определяет, будет ли на небольших экранах поверх стека объектов activity отображаться действие-заполнитель после того, как оно впервые появится в разделенном режиме с достаточной минимальной шириной.
Добавьте правило в WindowManager
RuleController:Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
Укажите действия, которые никогда не должны быть частью разделения:
Создайте
ActivityFilter, который определяет действие, которое всегда должно занимать всю область отображения задачи:Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
Добавьте фильтр в набор фильтров:
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
Как создать
ActivityRuleKotlin
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
Java
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
ActivityRule.Builderсоздает и настраивает правило:expandedActivityFilterSet– содержит фильтры действий, которые определяют, когда применять правило, идентифицируя действия, которые вы хотите исключить из разделения.setAlwaysExpand(): указывает, должна ли операция занимать все окно задачи.
Добавьте правило в WindowManager
RuleController:Kotlin
ruleController.addRule(activityRule)
Java
ruleController.addRule(activityRule);
Встраивание контента из разных приложений
В Android 13 (уровень API 33) и более поздних версиях приложения могут встраивать действия из других приложений. Встраивание действий из нескольких приложений Android позволяет визуально интегрировать действия из разных приложений или разных UID. Система отображает на экране рядом или друг над другом активность хост-приложения и встроенную активность из другого приложения так же, как при встраивании активности в одном приложении.
Например, приложение "Настройки" может встроить функцию выбора обоев из приложения WallpaperPicker:
Модель доверия
Процессы хоста, в которые встроены действия из других приложений, могут переопределять представление встроенных действий, в том числе размер, положение, обрезку и прозрачность. Злоумышленники могут использовать эту функцию, чтобы вводить пользователей в заблуждение и создавать клики по поддельной ссылке или другие атаки с использованием подмены интерфейса.
Чтобы предотвратить злоупотребление функцией встраивания действий из других приложений, Android требует, чтобы приложения разрешали встраивать свои действия. Приложения могут назначать хосты доверенными или ненадежными.
Доверенные хосты
Чтобы разрешить другим приложениям встраивать и полностью контролировать представление действий из вашего приложения, укажите сертификат SHA-256 хост-приложения в атрибуте android:knownActivityEmbeddingCerts элементов <activity> или <application> файла манифеста вашего приложения.
Установите для параметра android:knownActivityEmbeddingCerts значение в виде строки:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
... />
или, если нужно указать несколько сертификатов, массив строк:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
... />
который ссылается на ресурс, например:
<resources>
<string-array name="known_host_certificate_digests">
<item>cert1</item>
<item>cert2</item>
...
</string-array>
</resources>
Владельцы приложений могут получить дайджест сертификата SHA, выполнив задачу Gradle signingReport. Дайджест сертификата – это цифровой отпечаток SHA-256 без разделяющих двоеточий. Подробнее о том, как создать отчет о подписи и как аутентифицировать клиента…
Недоверенные хосты
Чтобы разрешить любому приложению встраивать действия вашего приложения и управлять их представлением, укажите атрибут android:allowUntrustedActivityEmbedding в элементах <activity> или <application> в манифесте приложения, например:
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
Значение по умолчанию – false, что запрещает встраивание межприложенной активности.
Собственная аутентификация
Чтобы снизить риск внедрения ненадежных действий, создайте собственный механизм аутентификации, который проверяет личность хоста. Если вам известны сертификаты хоста, используйте библиотеку androidx.security.app.authenticator для аутентификации. Если организатор пройдет аутентификацию после того, как вы встроите занятие, вы сможете показать его реальное содержимое. Если нет, вы можете сообщить пользователю, что действие запрещено, и заблокировать контент.
Используйте метод ActivityEmbeddingController#isActivityEmbedded() из библиотеки Jetpack WindowManager, чтобы проверить, встраивает ли хост ваше действие. Пример:
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(context).isActivityEmbedded(activity); }
Ограничение по минимальному размеру
Система Android применяет минимальную высоту и ширину, указанные в элементе манифеста приложения <layout>, к встроенным действиям. Если приложение не задает минимальную высоту и ширину, используются системные значения по умолчанию (sw220dp).
Если хост попытается изменить размер встроенного контейнера на меньший, чем минимальный, встроенный контейнер развернется на всю область задачи.
<activity-alias>
Чтобы встраивание доверенной или недоверенной активности работало с элементом <activity-alias>, к целевой активности, а не к псевдониму, необходимо применить android:knownActivityEmbeddingCerts или android:allowUntrustedActivityEmbedding. Правило, которое проверяет безопасность на системном сервере, основано на флагах, заданных для целевого объекта, а не для псевдонима.
Хост-приложение
Встраивание объектов activity в несколько приложений выполняется так же, как и в одно. Объекты SplitPairRule и SplitPairFilter или ActivityRule и ActivityFilter определяют встроенные действия и разделение окна задач. Правила разделения определяются статически в XML или во время выполнения с помощью вызовов API Jetpack WindowManager.
Если хост-приложение пытается встроить действие, для которого не включено встраивание в другие приложения, действие занимает все границы задачи. Поэтому хост-приложения должны знать, разрешено ли в целевых действиях встраивание из других приложений.
Если встроенное действие запускает новое действие в той же задаче и новое действие не поддерживает встраивание в другие приложения, оно занимает всю область задачи, а не накладывается на действие во встроенном контейнере.
Встраивать собственные действия в хост-приложение можно без ограничений, если они запускаются в той же задаче.
Примеры разделения
Разделить окно
Рефакторинг не требуется. Вы можете задать конфигурацию для разделения статически или во время выполнения, а затем вызвать Context#startActivity() без дополнительных параметров.
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Разбиение по умолчанию
Если целевая страница приложения предназначена для разделения на два контейнера на больших экранах, лучше всего, чтобы оба действия создавались и отображались одновременно. Однако контент может быть недоступен для вторичного контейнера разделенного экрана, пока пользователь не взаимодействует с действием в основном контейнере (например, не выберет элемент в меню навигации). Пока в дополнительном контейнере не появится контент, его можно заменить на плейсхолдер (см. раздел Плейсхолдеры).
Чтобы создать разделение с помощью плейсхолдера, создайте плейсхолдер и свяжите его с основным действием:
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
Разделение по ссылкам на контент
Когда приложение получает намерение, целевое действие может быть показано как вторая часть разделенного действия, например запрос на показ экрана с информацией об элементе из списка. На небольших экранах подробная информация показывается в полном окне задач, а на больших устройствах – рядом со списком.
Запрос на запуск должен быть направлен в основное действие, а целевое действие с подробной информацией должно быть запущено в разделенном режиме. Система автоматически выбирает подходящий вариант представления (в столбик или рядом) в зависимости от доступной ширины экрана.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
Целевая страница, на которую ведет ссылка на контент, может быть единственным действием, доступным пользователю в стеке обратной навигации. В этом случае не следует закрывать Activity с подробной информацией и оставлять только главную Activity:


Вместо этого вы можете указать, что обе задачи выполняются одновременно, с помощью атрибута finishPrimaryWithSecondary:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
Подробнее об атрибутах конфигурации…
Несколько действий в разделенных контейнерах
Если в разделенном контейнере разместить несколько действий, пользователи смогут получить доступ к контенту. Например, при использовании макета «список и подробные сведения» пользователю может понадобиться перейти в раздел с дополнительной информацией, но при этом сохранить основное действие:
Kotlin
class DetailActivity : AppCompatActivity() { fun onOpenSubdetail() { startActivity(Intent(this, SubdetailActivity::class.java)) } }
Java
public class DetailActivity extends AppCompatActivity { void onOpenSubdetail() { startActivity(new Intent(this, SubdetailActivity.class)); } }
Дочернее действие размещается поверх родительского, скрывая его:

Чтобы вернуться к предыдущему уровню детализации, пользователь может перейти назад по стеку:
По умолчанию при запуске действий из действия в том же дополнительном контейнере они располагаются друг над другом. Действия, запущенные из основного контейнера в активном разделенном окне, также попадают во вторичный контейнер в верхней части стека объектов activity.
Действия в новой задаче
Если в окне разделенной задачи запускается новая задача, она будет отделена от исходной и отобразится в полноэкранном режиме. На экране "Недавние" показываются две задачи: задача в разделенном режиме и новая задача.
Замена активности
Действия можно заменять в стеке вторичного контейнера, например когда основное действие используется для навигации верхнего уровня, а вторичное действие – для выбранного пункта назначения. При выборе любого пункта в навигации верхнего уровня в дополнительном контейнере должно запускаться новое действие и удаляться предыдущее.
Если приложение не завершит Activity во вторичном контейнере при изменении выбора Navigation, обратная навигация может быть запутанной, когда разделение будет свернуто (устройство сложено). Например, если в основном окне есть меню, а во вспомогательном – экраны А и Б, то при складывании телефона экран Б будет поверх экрана А, а экран А – поверх меню. Когда пользователь возвращается из раздела Б, вместо меню появляется раздел А.
В таких случаях экран А должен быть удален из стека возврата.

По умолчанию при запуске в новом контейнере сбоку от существующего разделенного экрана новые дополнительные контейнеры размещаются поверх старых, которые сохраняются в стеке возврата. Вы можете настроить разделение так, чтобы предыдущие дополнительные контейнеры очищались с помощью clearTop, а новые действия запускались обычным образом.
<SplitPairRule
window:clearTop="true">
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenA"/>
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>
Kotlin
inner class MenuActivity : AppCompatActivity() { fun onMenuItemSelected(selectedMenuItem: Int) { startActivity(Intent(this, classForItem(selectedMenuItem))) } }
Java
public class MenuActivity extends AppCompatActivity{ void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
Также можно использовать одно и то же дополнительное действие, а из основного (меню) действия отправлять новые намерения, которые разрешаются в тот же экземпляр, но запускают обновление состояния или интерфейса в дополнительном контейнере.
Несколько разделений
Приложения могут обеспечивать многоуровневую навигацию, запуская дополнительные действия сбоку.
Когда действие во вспомогательном контейнере запускает новое действие сбоку, поверх существующего разделения создается новое.
Стек возврата содержит все ранее открытые действия, поэтому пользователи могут перейти к разделению A/B после завершения C.

Чтобы создать новый разделенный экран, запустите новое приложение сбоку от существующего дополнительного контейнера. Объявите конфигурации для разделения A/B и B/C и запустите Activity C обычным способом из Activity B:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
<SplitPairFilter
window:primaryActivityName=".B"
window:secondaryActivityName=".C"/>
</SplitPairRule>
Kotlin
class B : AppCompatActivity() { fun onOpenC() { startActivity(Intent(this, C::class.java)) } }
Java
public class B extends AppCompatActivity{ void onOpenC() { startActivity(new Intent(this, C.class)); } }
Как реагировать на изменения состояния разделения экрана
В разных действиях приложения могут быть элементы интерфейса, выполняющие одну и ту же функцию, например элемент управления, открывающий окно с настройками аккаунта.
Если два действия, у которых есть общий элемент интерфейса, находятся в разделенном режиме, показывать этот элемент в обоих действиях избыточно и может запутать пользователя.
Чтобы узнать, когда действия выполняются в разделенном режиме, проверьте поток SplitController.splitInfoList или зарегистрируйте прослушиватель с помощью SplitControllerCallbackAdapter для отслеживания изменений в разделенном режиме. Затем настройте интерфейс:
Kotlin
val layout = layoutInflater.inflate(R.layout.activity_main, null) val view = layout.findViewById<View>(R.id.infoButton) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance. .collect { list -> view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE } } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); new SplitControllerCallbackAdapter(SplitController.getInstance(this)) .addSplitListener( this, Runnable::run, splitInfoList -> { View layout = getLayoutInflater().inflate(R.layout.activity_main, null); layout.findViewById(R.id.infoButton).setVisibility( splitInfoList.isEmpty() ? View.VISIBLE : View.GONE); }); }
Корутины можно запускать в любом состоянии жизненного цикла, но обычно их запускают в состоянии STARTED, чтобы экономить ресурсы (подробнее об использовании корутин Kotlin с компонентами, учитывающими жизненный цикл…).
Обратные вызовы можно выполнять в любом состоянии жизненного цикла, в том числе когда действие остановлено. Прослушиватели обычно регистрируются в onStart() и отменяют регистрацию в onStop().
Полноэкранное модальное окно
Некоторые действия блокируют взаимодействие пользователя с приложением, пока не будет выполнено определенное действие, например вход в систему, подтверждение согласия с правилами или сообщение об ошибке. Модальные окна не должны появляться в режиме разделенного экрана.
Чтобы принудительно заполнить окно задачи, можно использовать конфигурацию expand:
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullWidthActivity"/>
</ActivityRule>
Завершать занятия
Чтобы завершить действие в одной из частей экрана, проведите пальцем от края дисплея:
Если на устройстве настроено использование кнопки "Назад" вместо навигации жестами, ввод отправляется в активное приложение – то, которое было запущено или выбрано последним.
Влияние завершения всех действий в контейнере на противоположный контейнер зависит от конфигурации разделения.
Атрибуты конфигурации
Вы можете указать атрибуты правила разделения, чтобы настроить, как завершение всех действий на одной стороне разделения влияет на действия на другой стороне. Атрибуты:
window:finishPrimaryWithSecondary– как завершение всех действий во второстепенном контейнере влияет на действия в основном контейнере.window:finishSecondaryWithPrimary– как завершение всех действий в основном контейнере влияет на действия во второстепенном контейнере.
Возможные значения атрибутов:
always– всегда завершайте действия в связанном контейнере.never– никогда не завершать действия в связанном контейнере.adjacent– завершает действия в связанном контейнере, когда два контейнера расположены рядом, но не когда они расположены друг над другом.
Пример:
<SplitPairRule
<!-- Do not finish primary container activities when all secondary container activities finish. -->
window:finishPrimaryWithSecondary="never"
<!-- Finish secondary container activities when all primary container activities finish. -->
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Конфигурация по умолчанию
Когда все действия в одном из контейнеров завершатся, оставшийся контейнер займет все окно:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>


совместно выполнять задания;
Автоматически завершать действия в основном контейнере, когда завершаются все действия во вспомогательном контейнере:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>


Завершать действия во вторичном контейнере автоматически, когда будут завершены все действия в основном контейнере:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>


Завершать действия вместе, когда будут выполнены все действия в основном или дополнительном контейнере:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>


Как выполнять несколько действий в контейнерах
Если в разделенном контейнере несколько действий расположены одно над другим, завершение действия внизу не приводит к автоматическому завершению действий сверху.
Например, если в дополнительном контейнере два элемента, C и B, причем C находится поверх B:

а конфигурация разделения определяется конфигурацией действий А и Б:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Если вы завершите верхнее занятие, разделение сохранится.

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

Также выполняются любые дополнительные правила для завершения занятий вместе, например правило о том, что второстепенное занятие должно быть завершено вместе с основным.
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>

Если в настройках указано, что основной и дополнительный сплит должны завершиться одновременно:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>



Как изменить свойства разделения во время выполнения
Нельзя изменить свойства активного и видимого разделения. Изменение правил разделения влияет на запуск дополнительных действий и новых контейнеров, но не на существующие и активные разделения.
Чтобы изменить свойства активных разделений, завершите побочное действие или действия в разделении и снова запустите побочное действие с новой конфигурацией.
Динамические свойства разделения
Android 15 (уровень API 35) и более поздние версии, поддерживаемые Jetpack WindowManager 1.4 и более поздними версиями, предлагают динамические функции, позволяющие настраивать разделение встроенных действий, в том числе:
- Разделение на панели. Интерактивный перетаскиваемый разделитель позволяет пользователям изменять размер панелей в разделенной презентации.
- Закрепление стека объектов activity. Пользователи могут закрепить контент в одном контейнере и изолировать навигацию в нем от навигации в другом контейнере.
- Затемнение экрана при показе диалогового окна. При показе диалогового окна приложения могут указывать, нужно ли затемнять все окно задачи или только контейнер, в котором открылось диалоговое окно.
Развертывание панели
Развертывание панели позволяет пользователям регулировать размер экрана, выделенный для двух действий в двухпанельном макете.
Чтобы настроить внешний вид разделителя окон и задать диапазон, в котором его можно перетаскивать, выполните следующие действия:
Как создать экземпляр
DividerAttributesНастройте атрибуты разделителя:
color: цвет разделителя перетаскиваемой панели.widthDp: ширина разделителя перетаскиваемой панели. Задайте значениеWIDTH_SYSTEM_DEFAULT, чтобы система определяла ширину разделителя.Диапазон перетаскивания. Минимальный процент экрана, который может занимать каждая панель. Значение может быть от 0,33 до 0,66. Задайте значение
DRAG_RANGE_SYSTEM_DEFAULT, чтобы система определяла диапазон перетаскивания.
Kotlin
val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) if (WindowSdkExtensions.getInstance().extensionVersion >= 6) { splitAttributesBuilder.setDividerAttributes( DividerAttributes.DraggableDividerAttributes.Builder() .setColor(getColor(R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ) } val splitAttributes: SplitAttributes = splitAttributesBuilder.build()
Java
SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT); if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { splitAttributesBuilder.setDividerAttributes( new DividerAttributes.DraggableDividerAttributes.Builder() .setColor(ContextCompat.getColor(this, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ); } SplitAttributes _splitAttributes = splitAttributesBuilder.build();
Закрепление стека объектов activity
Закрепление стека объектов activity позволяет закрепить одно из разделенных окон, чтобы действия в нем не менялись, пока пользователь работает в другом окне. Закрепление стека действий позволяет работать в режиме многозадачности.
Чтобы включить закрепление стека объектов activity в приложении, выполните следующие действия:
Добавьте кнопку в файл макета объекта activity, который вы хотите закрепить, например в объект activity с подробными сведениями в макете "Список и подробные сведения":
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/detailActivity" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" tools:context=".DetailActivity"> <TextView android:id="@+id/textViewItemDetail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="36sp" android:textColor="@color/obsidian" app:layout_constraintBottom_toTopOf="@id/pinButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.appcompat.widget.AppCompatButton android:id="@+id/pinButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pin_this_activity" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/> </androidx.constraintlayout.widget.ConstraintLayout>В методе
onCreate()активности задайте прослушиватель кликов для кнопки:Kotlin
val pinButton: Button = findViewById(R.id.pinButton) pinButton.setOnClickListener { val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build() val pinSplitRule = SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build() SplitController.getInstance(applicationContext) .pinTopActivityStack(taskId, pinSplitRule) }
Java
Button pinButton = findViewById(R.id.pinButton); pinButton.setOnClickListener( (view) -> { SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build(); SplitPinRule pinSplitRule = new SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build(); SplitController.getInstance(getApplicationContext()) .pinTopActivityStack(getTaskId(), pinSplitRule); });
Диалоговое окно с затемнением экрана
Обычно экраны действий затемняются, чтобы привлечь внимание к диалоговому окну. При встраивании действий должны затемняться обе панели двухпанельного дисплея, а не только та, на которой открылось диалоговое окно. Это необходимо для единообразия интерфейса.
В WindowManager версии 1.4 и более поздних версий при открытии диалогового окна по умолчанию затемняется все окно приложения (см. EmbeddingConfiguration.DimAreaBehavior.ON_TASK).
Чтобы затемнить только контейнер действия, которое открыло диалоговое окно, используйте EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.
Как извлечь действие из разделенного экрана в полноэкранный режим
Создайте новую конфигурацию, в которой боковое действие будет отображаться в полноэкранном режиме, а затем перезапустите действие с намерением, которое разрешается в тот же экземпляр.
Как проверить поддержку разделения во время выполнения
Встраивание действий поддерживается в Android 12L (уровень API 32) и более поздних версиях, а также на некоторых устройствах с более ранними версиями платформы. Чтобы проверить доступность функции во время выполнения, используйте свойство SplitController.splitSupportStatus или метод SplitController.getSplitSupportStatus():
Kotlin
if (SplitController.getInstance(this).splitSupportStatus == SplitController.SplitSupportStatus.SPLIT_AVAILABLE ) { // Device supports split activity features. }
Java
if (SplitController.getInstance(this).getSplitSupportStatus() == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
Если разделение экрана не поддерживается, действия запускаются поверх стека действий (в соответствии с моделью встраивания, не связанной с действиями).
Как предотвратить переопределение системных настроек
Производители устройств Android (производители оригинального оборудования, или OEM) могут встроить функцию в системное ПО устройства. Система задает правила разделения для приложений с несколькими действиями, переопределяя поведение окон приложений. Системная переопределяющая функция принудительно переводит многоэкранные приложения в режим встраивания, заданный системой.
Встраивание системных действий может улучшить представление приложения с помощью многопанельных макетов, таких как список и подробные сведения, без каких-либо изменений в приложении. Однако встраивание системных действий также может привести к неправильным макетам приложений, ошибкам или конфликтам со встраиванием действий, реализованным приложением.
Чтобы запретить или разрешить встраивание системных действий, задайте значение PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE в файле манифеста приложения, например:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
android:value="true|false" />
</application>
</manifest>
Название свойства определяется в объекте Jetpack WindowManager WindowProperties. Установите значение false, если в вашем приложении реализовано встраивание операций или если вы хотите запретить системе применять к нему правила встраивания операций. Установите значение true, чтобы разрешить системе применять к вашему приложению определенное системой встраивание операций.
Ограничения и примечания
- Только приложение, запустившее задачу, может организовывать и встраивать другие действия в задачу. Это приложение определяется как владелец корневого действия в задаче. Если активности, поддерживающие встраивание и разделение, выполняются в задаче, принадлежащей другому приложению, то встраивание и разделение для этих активностей работать не будут.
- Действия можно организовать только в рамках одной задачи. При запуске действия в новой задаче оно всегда открывается в новом развернутом окне вне существующих разделений.
- Разделить можно только действия, выполняемые в одном процессе. Функция обратного вызова
SplitInfoсообщает только о действиях, относящихся к одному и тому же процессу, поскольку нет возможности узнать о действиях в других процессах. - Каждое правило для пары или одного действия применяется только к действиям, запущенным после регистрации правила. В настоящее время нельзя изменить существующие разделы или их визуальные свойства.
- Конфигурация фильтра для разделения пары должна полностью соответствовать намерениям, используемым при запуске действий. Сопоставление происходит в момент запуска нового действия из процесса приложения, поэтому оно может не знать о названиях компонентов, которые разрешаются позже в системном процессе при использовании неявных намерений. Если имя компонента неизвестно на момент запуска, вместо него можно использовать подстановочный знак ("*/*"), а фильтрацию можно выполнять на основе действия интента.
- В настоящее время нельзя перемещать действия между контейнерами или в разделы и из них после создания. Разделение экрана выполняется только библиотекой WindowManager при запуске новых действий с соответствующими правилами. Разделение экрана прекращается, когда завершается последнее действие в контейнере разделения.
- Объекты activity можно перезапускать при изменении конфигурации, поэтому при создании или удалении разделения экрана и изменении границ объекта activity может быть полностью уничтожен предыдущий экземпляр и создан новый. Поэтому разработчикам приложений следует осторожно использовать такие функции, как запуск новых действий из обратных вызовов жизненного цикла.
- Устройства должны включать интерфейс расширений окон, чтобы поддерживать встраивание действий. Интерфейс есть почти на всех устройствах с большим экраном, на которых установлена ОС Android 12L (уровень API 32) или более поздней версии. Однако некоторые устройства с большим экраном, которые не могут запускать несколько действий, не включают интерфейс расширений окон. Если устройство с большим экраном не поддерживает многооконный режим, оно может не поддерживать и встраивание действий.
Дополнительные ресурсы
- Практические работы:
- Учебный план – встраивание объектов activity
- Пример приложения – activity-embedding