Osadzanie aktywności optymalizuje aplikacje na urządzeniach z dużym ekranem, dzieląc okno zadania aplikacji na 2 aktywności lub 2 wystąpienia tej samej aktywności.
 
  Jeśli Twoja aplikacja składa się z wielu aktywności, osadzanie aktywności umożliwia zapewnienie lepszego komfortu użytkowania na tabletach, urządzeniach składanych i urządzeniach z ChromeOS.
Osadzanie aktywności nie wymaga refaktoryzacji kodu. Sposób wyświetlania aktywności aplikacji – obok siebie lub jedna na drugiej – możesz określić, tworząc plik konfiguracji XML lub wywołując interfejs Jetpack WindowManager API.
Obsługa małych ekranów jest utrzymywana automatycznie. Gdy aplikacja jest uruchomiona na urządzeniu z małym ekranem, aktywności są ułożone jedna na drugiej. Na dużych ekranach aktywności są wyświetlane obok siebie. System określa sposób prezentacji na podstawie utworzonej przez Ciebie konfiguracji. Nie wymaga to logiki rozgałęzienia.
Osadzanie aktywności dostosowuje się do zmian orientacji urządzenia i działa bezproblemowo na urządzeniach składanych, układając i rozdzielając aktywności w miarę składania i rozkładania urządzenia.
Osadzanie aktywności jest obsługiwane na większości urządzeń z dużym ekranem z Androidem 12L (API na poziomie 32) lub nowszym.
Podziel okno zadania
Osadzanie aktywności dzieli okno zadania aplikacji na 2 kontenery: główny i dodatkowy. Kontenery zawierają działania uruchomione z głównego działania lub z innych działań, które już się w nich znajdują.
Aktywności są układane w kontenerze dodatkowym w kolejności ich uruchamiania, a kontener dodatkowy jest układany nad kontenerem głównym na małych ekranach, więc układanie aktywności i nawigacja wstecz są zgodne z kolejnością aktywności wbudowaną już w aplikację.
Osadzanie aktywności umożliwia wyświetlanie aktywności na różne sposoby. Aplikacja może podzielić okno zadania, uruchamiając 2 aktywności jednocześnie obok siebie lub jedną nad drugą:
 
   
  Aktywność zajmująca całe okno zadania może utworzyć podział, uruchamiając nową aktywność obok:
 
  Aktywności, które są już w oknie podzielonym i współdzielą okno zadania, mogą uruchamiać inne aktywności w następujący sposób:
- Obok innej aktywności:   - Rysunek 4. Aktywność A uruchamia aktywność C z boku, nad aktywnością B. 
- na bok i przesuń podział w bok, ukrywając poprzednią aktywność główną:   - Rysunek 5. Aktywność B uruchamia aktywność C z boku i przesuwa podział na bok. 
- Uruchom działanie w miejscu na górze, czyli w tym samym stosie działań:   - Rysunek 6. Aktywność B uruchamia aktywność C bez dodatkowych flag intencji. 
- Uruchom aktywność w pełnym oknie w ramach tego samego zadania:   - Rysunek 7. Aktywność A lub aktywność B uruchamia aktywność C, która wypełnia okno zadania. 
Przechodzenie wstecz
Różne typy aplikacji mogą mieć różne reguły nawigacji wstecznej w stanie okna podzielonego zadania w zależności od zależności między aktywnościami lub sposobu, w jaki użytkownicy wywołują zdarzenie powrotu, np.:
- Powiązane działania: jeśli działania są powiązane i jedno nie powinno być wyświetlane bez drugiego, można skonfigurować nawigację wstecz, aby zakończyć oba działania.
- Samodzielne działanie: jeśli aktywności są całkowicie niezależne, nawigacja wstecz w jednej z nich nie wpływa na stan innej aktywności w oknie zadania.
Zdarzenie wstecz jest wysyłane do ostatniej aktywności, na której skupiono uwagę, gdy używana jest nawigacja za pomocą przycisków.
W przypadku nawigacji przy użyciu gestów:
- Android 14 (poziom 34 interfejsu API) i starszy – zdarzenie powrotu jest wysyłane do aktywności, w której wykonano gest. Gdy użytkownicy przesuną palcem od lewej strony ekranu, do aktywności w lewym okienku podzielonego ekranu zostanie wysłane zdarzenie wstecz. Gdy użytkownicy przesuną palcem od prawej strony ekranu, zdarzenie wstecz zostanie wysłane do aktywności w panelu po prawej stronie. 
- Android 15 (poziom 35 interfejsu API) lub nowszy - W przypadku wielu aktywności z tej samej aplikacji gest kończy aktywność na górze niezależnie od kierunku przesunięcia, co zapewnia bardziej spójne działanie. 
- W przypadku 2 aktywności z różnych aplikacji (nakładki) zdarzenie powrotu jest kierowane do ostatniej aktywności, która była w centrum uwagi, co jest zgodne z działaniem nawigacji za pomocą przycisków. 
 
Układ wielopanelowy
Biblioteka Jetpack WindowManager umożliwia tworzenie układów z wieloma panelami osadzonymi w aktywności na urządzeniach z dużym ekranem z Androidem 12L (poziom interfejsu API 32) lub nowszym oraz na niektórych urządzeniach z wcześniejszymi wersjami platformy. Istniejące aplikacje, które są oparte na wielu działaniach, a nie na fragmentach lub układach opartych na widokach, takich jak SlidingPaneLayout, mogą zapewnić lepsze wrażenia użytkownika na dużym ekranie bez refaktoryzacji kodu źródłowego.
Jednym z częstych przykładów jest podział na listę i szczegóły. Aby zapewnić wysoką jakość prezentacji, system uruchamia aktywność listy, a następnie aplikacja natychmiast uruchamia aktywność szczegółów. System przejścia czeka, aż obie aktywności zostaną narysowane, a następnie wyświetla je razem. Dla użytkownika te 2 działania uruchamiają się jako jedno.
 
  Dzielenie atrybutów
Możesz określić, jak okno zadania jest podzielone między kontenery i jak są one ułożone względem siebie.
W przypadku reguł zdefiniowanych w pliku konfiguracji XML ustaw te atrybuty:
- splitRatio: określa proporcje kontenera. Wartość jest liczbą zmiennoprzecinkową z przedziału otwartego (0,0, 1,0).
- splitLayoutDirection: określa sposób rozmieszczenia podzielonych kontenerów względem siebie. Dostępne wartości:- ltr: od lewej do prawej
- rtl: od prawej do lewej
- locale: wartość- ltrlub- rtljest określana na podstawie ustawień regionalnych
 
Przykłady znajdziesz w sekcji Konfiguracja XML.
W przypadku reguł utworzonych przy użyciu interfejsów API WindowManager utwórz obiekt SplitAttributes z SplitAttributes.Builder i wywołaj te metody narzędzia do tworzenia:
- setSplitType(): określa proporcje podzielonych kontenerów. Prawidłowe argumenty, w tym metodę- SplitAttributes.SplitType.ratio(), znajdziesz w artykule- SplitAttributes.SplitType.
- setLayoutDirection(): określa układ kontenerów. Możliwe wartości znajdziesz w sekcji- SplitAttributes.LayoutDirection.
Przykłady znajdziesz w sekcji WindowManager API.
 
  Orientacja podzielonego ekranu
Wymiary i proporcje wyświetlacza określają położenie aktywności w podziałach osadzania aktywności. Na dużych wyświetlaczach w orientacji poziomej aktywności są wyświetlane obok siebie, a na wysokich wyświetlaczach w orientacji pionowej lub na urządzeniach składanych w pozycji stołowej – jedna nad drugą.
Orientację podziału możesz określić za pomocą kalkulatora SplitController
SplitAttributes. Kalkulator oblicza SplitAttributes dla aktywnego SplitRule.
Użyj kalkulatora, aby podzielić kontener nadrzędny w różnych kierunkach dla różnych stanów urządzenia, np.:
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(); } }); }
Na urządzeniach składanych możesz podzielić ekran pionowo, jeśli urządzenie jest w orientacji poziomej, wyświetlić pojedynczą aktywność, jeśli urządzenie jest w orientacji pionowej, i podzielić ekran poziomo, jeśli urządzenie jest ustawione na stole:
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(); } }); }
Obiekty zastępcze
Aktywności zastępcze to puste aktywności dodatkowe, które zajmują obszar podziału aktywności. Ostatecznie mają one zostać zastąpione inną aktywnością zawierającą treści. Na przykład aktywność zastępcza może zajmować drugą stronę podziału aktywności w układzie lista-szczegóły, dopóki nie zostanie wybrany element z listy. W tym momencie aktywność zawierająca szczegółowe informacje o wybranym elemencie listy zastąpi aktywność zastępczą.
Domyślnie system wyświetla symbole zastępcze tylko wtedy, gdy jest wystarczająco dużo miejsca na podział aktywności. Miejsca docelowe są automatycznie zamykane, gdy rozmiar wyświetlacza zmieni się na szerokość lub wysokość zbyt małą, aby wyświetlić podział. Gdy jest to możliwe, system ponownie uruchamia element zastępczy w zresetowanym stanie.
 
  Atrybut stickyPlaceholder metody SplitPlaceholderRule lub setSticky() interfejsu SplitPlaceholder.Builder może jednak zastąpić domyślne działanie. Jeśli atrybut lub metoda określa wartość true, system wyświetla element zastępczy jako aktywność na samej górze okna zadania, gdy rozmiar ekranu zostanie zmniejszony z 2-panelowego do 1-panelowego (przykład znajdziesz w sekcji Konfiguracja podziału).
 
  Zmiany rozmiaru okna
Gdy zmiany konfiguracji urządzenia zmniejszą szerokość okna zadania tak, że nie będzie ono wystarczająco duże, aby pomieścić układ wielopanelowy (np. gdy duże urządzenie składane z ekranem o rozmiarze tabletu zostanie złożone do rozmiaru telefonu lub gdy rozmiar okna aplikacji zostanie zmieniony w trybie wielu okien), aktywności inne niż zastępcze w panelu dodatkowym okna zadania zostaną ułożone jedna na drugiej nad aktywnościami w panelu głównym.
Zastępcze aktywności są wyświetlane tylko wtedy, gdy jest wystarczająca szerokość ekranu, aby podzielić go na 2 części. Na mniejszych ekranach element zastępczy jest automatycznie zamykany. Gdy obszar wyświetlania znów będzie wystarczająco duży, element zastępczy zostanie utworzony ponownie. (Patrz sekcja Symbole zastępcze).
Nakładanie aktywności jest możliwe, ponieważ WindowManager określa kolejność aktywności w panelu dodatkowym powyżej aktywności w panelu głównym.
Wiele aktywności w panelu dodatkowym
Aktywność B uruchamia aktywność C w miejscu bez dodatkowych flag intencji:

W rezultacie kolejność działań w tym samym zadaniu będzie następująca:

W mniejszym oknie zadania aplikacja zmniejsza się do pojedynczej aktywności z aktywnością C na górze stosu:

Cofanie się w mniejszym oknie powoduje przełączanie się między aktywnościami ułożonymi jedna na drugiej.
Jeśli okno zadania zostanie przywrócone do większego rozmiaru, który może pomieścić wiele paneli, aktywności będą ponownie wyświetlane obok siebie.
Skumulowane podziały
Aktywność B uruchamia aktywność C z boku i przesuwa podział na bok:

W rezultacie kolejność działań w tym samym zadaniu będzie następująca:

W mniejszym oknie zadania aplikacja zmniejsza się do pojedynczej aktywności z aktywnością C na górze:

Stała orientacja pionowa
Ustawienie manifestu android:screenOrientation umożliwia aplikacjom ograniczenie aktywności do orientacji pionowej lub poziomej. Aby zwiększyć wygodę użytkowników urządzeń z dużym ekranem, takich jak tablety i urządzenia składane, producenci urządzeń (OEM) mogą ignorować prośby o orientację ekranu i wyświetlać aplikację w formacie letterbox w orientacji pionowej na wyświetlaczach poziomych lub w orientacji poziomej na wyświetlaczach pionowych.
 
    Podobnie, gdy włączone jest osadzanie aktywności, producenci OEM mogą dostosowywać urządzenia do wyświetlania aktywności w orientacji pionowej w formacie letterbox w orientacji poziomej na dużych ekranach (szerokość ≥ 600 dp). Gdy działanie o stałej orientacji pionowej uruchamia drugie działanie, urządzenie może wyświetlać oba działania obok siebie na ekranie dwupanelowym.
 
  Zawsze dodawaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
do pliku manifestu aplikacji, aby poinformować urządzenia, że aplikacja obsługuje osadzanie aktywności (patrz sekcja Konfiguracja podziału). Urządzenia dostosowane przez producentów OEM mogą wtedy określać, czy wyświetlać aktywności w stałej orientacji pionowej w formacie letterbox.
Konfiguracja podziału
Reguły podziału konfigurują podziały aktywności. Reguły podziału określa się w pliku konfiguracji XML lub przez wywoływanie interfejsu API WindowManager Jetpacka.
W obu przypadkach aplikacja musi mieć dostęp do biblioteki WindowManager i informować system, że zaimplementowała osadzanie aktywności.
Wykonaj te czynności:
- Dodaj najnowszą zależność biblioteki WindowManager do pliku - build.gradlena poziomie modułu aplikacji, na przykład:- implementation 'androidx.window:window:1.1.0-beta02'- Biblioteka WindowManager zawiera wszystkie komponenty wymagane do osadzania aktywności. 
- Poinformuj system, że Twoja aplikacja implementuje osadzanie aktywności. - Dodaj właściwość - android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLEDdo elementu <application> w pliku manifestu aplikacji i ustaw wartość na „true”, np.:- <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>- W przypadku WindowManager w wersji 1.1.0-alpha06 i nowszych podziały osadzania aktywności są wyłączone, chyba że właściwość zostanie dodana do manifestu i ustawiona na wartość „true”. - Producenci urządzeń używają tego ustawienia również do włączania niestandardowych funkcji w aplikacjach obsługujących osadzanie aktywności. Na przykład urządzenia mogą wyświetlać aktywność w trybie pionowym na ekranach w trybie poziomym, aby dostosować ją do przejścia na układ dwupanelowy, gdy rozpocznie się druga aktywność (patrz Stała orientacja pionowa). 
Konfiguracja XML
Aby utworzyć implementację osadzania aktywności opartą na XML, wykonaj te czynności:
- Utwórz plik zasobu XML, który: - Określa działania, które mają wspólny podział
- Konfigurowanie opcji podziału
- Tworzy element zastępczy dla dodatkowego kontenera podziału, gdy treść jest niedostępna.
- Określa działania, które nigdy nie powinny być częścią podziału.
 - Na przykład: - <!-- 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>
- Utwórz inicjator. - Komponent WindowManager - RuleControlleranalizuje plik konfiguracji XML i udostępnia reguły systemowi. Biblioteka Jetpack Startup- Initializerudostępnia plik XML- RuleControllerpodczas uruchamiania aplikacji, dzięki czemu reguły obowiązują od momentu rozpoczęcia działania dowolnych aktywności.- Aby utworzyć inicjator, wykonaj te czynności: - Dodaj najnowszą zależność biblioteki Jetpack Startup do pliku na poziomie modułu, np.: - build.gradle- implementation 'androidx.startup:startup-runtime:1.1.1'
- Utwórz klasę, która implementuje interfejs - Initializer.- Inicjator udostępnia reguły podziału programowi - RuleController, przekazując identyfikator pliku konfiguracji XML (- main_split_config.xml) do metody- 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(); } } 
 
- Utwórz dostawcę treści dla definicji reguł. - Dodaj - androidx.startup.InitializationProviderdo pliku manifestu aplikacji jako- <provider>. Dodaj odwołanie do implementacji inicjatora- RuleController- SplitInitializer:- <!-- 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>- InitializationProviderwykrywa i inicjuje- SplitInitializerprzed wywołaniem metody- onCreate()aplikacji. W rezultacie reguły podziału obowiązują od momentu uruchomienia głównej aktywności aplikacji.
WindowManager API
Osadzanie aktywności możesz zaimplementować programowo za pomocą kilku wywołań interfejsu API. Wywołuj te funkcje w metodzie onCreate() podklasy Application, aby mieć pewność, że reguły będą obowiązywać przed rozpoczęciem jakichkolwiek działań.
Aby utworzyć podział aktywności za pomocą kodu:
- Utwórz regułę podziału: - Utwórz - SplitPairFilter, który identyfikuje aktywności, które mają wspólny podział:- 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 ); 
- Dodaj filtr do zestawu filtrów: ```- Kotlin- val filterSet = setOf(splitPairFilter) - Java- Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter); 
- Utwórz atrybuty układu dla podziału: - 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.Buildertworzy obiekt zawierający atrybuty układu:- setSplitType(): określa, jak dostępny obszar wyświetlania jest przydzielany do poszczególnych kontenerów aktywności. Typ podziału proporcji określa proporcję dostępnego obszaru wyświetlania przydzielonego do kontenera głównego. Kontener dodatkowy zajmuje pozostałą część dostępnego obszaru wyświetlania.
- setLayoutDirection(): określa sposób rozmieszczenia kontenerów aktywności względem siebie, przy czym pierwszy jest kontenerem głównym.
 
- Tworzenie - 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.Buildertworzy i konfiguruje regułę:- filterSet: zawiera filtry par podziału, które określają, kiedy zastosować regułę, identyfikując aktywności, które mają wspólny podział.
- setDefaultSplitAttributes(): stosuje atrybuty układu do reguły.
- setMinWidthDp(): określa minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości, dp), która umożliwia podział.
- setMinSmallestWidthDp(): określa minimalną wartość (w dp), jaką musi mieć mniejszy z 2 wymiarów wyświetlacza, aby włączyć podział niezależnie od orientacji urządzenia.
- setMaxAspectRatioInPortrait(): określa maksymalny współczynnik proporcji wyświetlania (wysokość:szerokość) w orientacji pionowej, dla którego wyświetlane są podziały aktywności. Jeśli współczynnik proporcji wyświetlacza w orientacji pionowej przekracza maksymalny współczynnik proporcji, podziały są wyłączone niezależnie od szerokości wyświetlacza. Uwaga: wartość domyślna to 1,4, co powoduje, że aktywności zajmują całe okno zadania w orientacji pionowej na większości tabletów. Zobacz też- SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTi- setMaxAspectRatioInLandscape(). Wartością domyślną w przypadku orientacji poziomej jest- ALWAYS_ALLOW.
- setFinishPrimaryWithSecondary(): określa, jak zakończenie wszystkich działań w kontenerze dodatkowym wpływa na działania w kontenerze głównym.- NEVERoznacza, że system nie powinien kończyć głównych działań, gdy wszystkie działania w kontenerze pomocniczym zostaną zakończone (patrz Zakończ działania).
- setFinishSecondaryWithPrimary(): określa, jak zakończenie wszystkich działań w kontenerze głównym wpływa na działania w kontenerze dodatkowym.- ALWAYSoznacza, że system powinien zawsze kończyć działania w kontenerze dodatkowym, gdy wszystkie działania w kontenerze głównym zostaną zakończone (patrz Zakończ działania).
- setClearTop(): określa, czy wszystkie aktywności w kontenerze dodatkowym są kończone, gdy w kontenerze zostanie uruchomiona nowa aktywność. Wartość- falseoznacza, że nowe aktywności są układane na aktywnościach, które już znajdują się w kontenerze dodatkowym.
 
- Pobierz instancję singletona WindowManager - RuleControlleri dodaj regułę:- Kotlin- val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule) - Java- RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule); 
- Utwórz element zastępczy dla kontenera dodatkowego, gdy treść jest niedostępna: 
- Utwórz - ActivityFilter, który identyfikuje aktywność, z którą symbol zastępczy dzieli okno zadania:- Kotlin- val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null ) - Java- ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null ); 
- Dodaj filtr do zestawu filtrów: - Kotlin- val placeholderActivityFilterSet = setOf(placeholderActivityFilter) - Java- Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter); 
- Tworzenie - 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.Buildertworzy i konfiguruje regułę:- placeholderActivityFilterSet: zawiera filtry aktywności, które określają, kiedy zastosować regułę, identyfikując aktywności, z którymi powiązana jest aktywność zastępcza.
- Intent: określa uruchomienie aktywności zastępczej.
- setDefaultSplitAttributes(): stosuje atrybuty układu do reguły.
- setMinWidthDp(): ustawia minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości, dp), która umożliwia podział.
- setMinSmallestWidthDp(): ustawia minimalną wartość (w dp), jaką musi mieć mniejszy z 2 wymiarów wyświetlacza, aby umożliwić podział niezależnie od orientacji urządzenia.
- setMaxAspectRatioInPortrait(): ustawia maksymalny współczynnik proporcji wyświetlania (wysokość:szerokość) w orientacji pionowej, dla którego wyświetlane są podziały aktywności. Uwaga: wartość domyślna to 1,4, co powoduje, że aktywności wypełniają okno zadania w orientacji pionowej na większości tabletów. Zobacz też- SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTi- setMaxAspectRatioInLandscape(). Wartością domyślną w przypadku orientacji poziomej jest- ALWAYS_ALLOW.
- setFinishPrimaryWithPlaceholder(): Określa, jak zakończenie aktywności w obiekcie zastępczym wpływa na aktywności w kontenerze głównym. ALWAYS oznacza, że system powinien zawsze kończyć działania w kontenerze podstawowym, gdy kończy się działanie w kontenerze zastępczym (patrz Kończenie działań).
- setSticky(): określa, czy aktywność zastępcza ma się pojawiać na górze stosu aktywności na małych wyświetlaczach po tym, jak po raz pierwszy pojawiła się w podziale o odpowiedniej minimalnej szerokości.
 
- Dodaj regułę do - RuleControllerWindowManager:- Kotlin- ruleController.addRule(splitPlaceholderRule) - Java- ruleController.addRule(splitPlaceholderRule); 
 
- Określ czynności, które nigdy nie powinny być częścią podziału: - Utwórz element - ActivityFilter, który identyfikuje aktywność, która powinna zawsze zajmować cały obszar wyświetlania zadania:- Kotlin- val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null ) - Java- ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null ); 
- Dodaj filtr do zestawu filtrów: - Kotlin- val expandedActivityFilterSet = setOf(expandedActivityFilter) - Java- Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter); 
- Utwórz - ActivityRule:- Kotlin- val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build() - Java- ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build(); - ActivityRule.Buildertworzy i konfiguruje regułę:- expandedActivityFilterSet: zawiera filtry aktywności, które określają, kiedy należy zastosować regułę, identyfikując aktywności, które chcesz wykluczyć z podziałów.
- setAlwaysExpand(): określa, czy aktywność powinna wypełniać całe okno zadania.
 
- Dodaj regułę do - RuleControllerWindowManager:- Kotlin- ruleController.addRule(activityRule) - Java- ruleController.addRule(activityRule); 
 
Umieszczanie w różnych aplikacjach
Na Androidzie 13 (API na poziomie 33) i nowszym aplikacje mogą osadzać aktywności z innych aplikacji. Osadzanie aktywności w różnych aplikacjach lub w różnych identyfikatorach użytkownika umożliwia wizualną integrację aktywności z wielu aplikacji na Androida. System wyświetla aktywność aplikacji hosta i aktywność zagnieżdżoną z innej aplikacji obok siebie lub jedna pod drugą, tak jak w przypadku zagnieżdżania aktywności w jednej aplikacji.
Na przykład aplikacja Ustawienia może osadzić aktywność selektora tapet z aplikacji WallpaperPicker:
 
    Model zaufania
Procesy hosta, które osadzają aktywności z innych aplikacji, mogą zmieniać sposób prezentacji osadzonych aktywności, w tym ich rozmiar, położenie, przycinanie i przezroczystość. Złośliwe hosty mogą wykorzystywać tę funkcję do wprowadzania użytkowników w błąd i przeprowadzania ataków typu clickjacking lub innych ataków polegających na modyfikowaniu interfejsu.
Aby zapobiec niewłaściwemu wykorzystaniu osadzania aktywności w różnych aplikacjach, Android wymaga, aby aplikacje wyrażały zgodę na osadzanie swoich aktywności. Aplikacje mogą oznaczać hosty jako zaufane lub niezaufane.
Zaufane hosty
Aby umożliwić innym aplikacjom osadzanie i pełne kontrolowanie prezentacji działań z Twojej aplikacji, w atrybucie android:knownActivityEmbeddingCerts elementu <activity> lub <application> w pliku manifestu aplikacji określ certyfikat SHA-256 aplikacji hosta.
Ustaw wartość android:knownActivityEmbeddingCerts jako ciąg znaków:
<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />
lub, aby określić wiele certyfikatów, tablicę ciągów znaków:
<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />
który odwołuje się do zasobu takiego jak ten:
<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>
Właściciele aplikacji mogą uzyskać skrót certyfikatu SHA, uruchamiając zadanie Gradle
signingReport. Skrót certyfikatu to odcisk cyfrowy SHA-256 bez dwukropków oddzielających. Więcej informacji znajdziesz w sekcjach Uruchamianie raportu podpisywania i Uwierzytelnianie klienta.
Niezaufane hosty
Aby zezwolić dowolnej aplikacji na osadzanie działań Twojej aplikacji i kontrolowanie ich prezentacji, w elementach <activity> lub <application> w pliku manifestu aplikacji określ atrybut android:allowUntrustedActivityEmbedding, na przykład:
<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />
Domyślna wartość atrybutu to false, co uniemożliwia osadzanie aktywności w innych aplikacjach.
Uwierzytelnianie niestandardowe
Aby ograniczyć ryzyko związane z osadzaniem niezaufanej aktywności, utwórz niestandardowy mechanizm uwierzytelniania, który weryfikuje tożsamość hosta. Jeśli znasz certyfikaty hosta, użyj biblioteki androidx.security.app.authenticator do uwierzytelniania. Jeśli gospodarz uwierzytelni się po osadzeniu Twojej aktywności, możesz wyświetlić rzeczywistą treść. Jeśli nie, możesz poinformować użytkownika, że działanie jest niedozwolone, i zablokować treści.
Użyj metody ActivityEmbeddingController#isActivityEmbedded() z biblioteki Jetpack WindowManager, aby sprawdzić, czy host osadza Twoją aktywność, np.:
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(context).isActivityEmbedded(activity); }
Ograniczenie minimalnego rozmiaru
System Android stosuje minimalną wysokość i szerokość określoną w elemencie <layout> pliku manifestu aplikacji do osadzonych aktywności. Jeśli aplikacja nie określi minimalnej wysokości i szerokości, zostaną zastosowane domyślne wartości systemowe (sw220dp).
Jeśli host spróbuje zmienić rozmiar osadzonego kontenera na mniejszy niż minimalny, osadzony kontener rozszerzy się, aby zajmować całą przestrzeń zadania.
<activity-alias>
Aby osadzanie zaufanych lub niezaufanych działań działało z elementem 
<activity-alias>, do działania docelowego należy zastosować android:knownActivityEmbeddingCerts lub 
android:allowUntrustedActivityEmbedding, a nie alias. Zasady weryfikujące bezpieczeństwo na serwerze systemowym są oparte na flagach ustawionych na celu, a nie na aliasie.
Aplikacja hostująca
Aplikacje hostujące implementują osadzanie aktywności w różnych aplikacjach w taki sam sposób jak osadzanie aktywności w jednej aplikacji. Obiekty SplitPairRule i SplitPairFilter lub ActivityRule i ActivityFilter określają osadzone aktywności i podziały okna zadań. Reguły podziału są definiowane statycznie w XML lub w czasie działania za pomocą wywołań interfejsu Jetpack WindowManager API.
Jeśli aplikacja hostująca spróbuje osadzić aktywność, która nie została włączona do osadzania w innych aplikacjach, aktywność zajmie cały obszar zadania. W związku z tym aplikacje hosta muszą wiedzieć, czy docelowe aktywności zezwalają na osadzanie w innych aplikacjach.
Jeśli osadzona aktywność rozpocznie nową aktywność w tym samym zadaniu, a nowa aktywność nie będzie korzystać z osadzania w innych aplikacjach, zajmie ona całe granice zadania, zamiast nakładać się na aktywność w osadzonym kontenerze.
Aplikacja hostująca może osadzać własne działania bez ograniczeń, o ile są one uruchamiane w ramach tego samego zadania.
Przykłady podziału
Dzielenie ekranu z poziomu pełnego okna
 
  Nie wymaga refaktoryzacji. Konfigurację podziału możesz zdefiniować statycznie lub w czasie działania, a następnie wywołać funkcję Context#startActivity() bez dodatkowych parametrów.
<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>
Domyślnie podzielone
Jeśli strona docelowa aplikacji jest przeznaczona do podzielenia na 2 kontenery na dużych ekranach, najlepsze wrażenia użytkownika są wtedy, gdy oba działania są tworzone i wyświetlane jednocześnie. Treści mogą jednak nie być dostępne w przypadku drugiego kontenera podzielonego ekranu, dopóki użytkownik nie wejdzie w interakcję z aktywnością w kontenerze głównym (np. nie wybierze elementu z menu nawigacyjnego). Aktywność zastępcza może wypełnić lukę, dopóki w drugim kontenerze podziału nie będzie można wyświetlić treści (patrz sekcja Obiekty zastępcze).
 
  Aby utworzyć podział z symbolem zastępczym, utwórz symbol zastępczy i powiąż go z aktywnością podstawową:
<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
Podział precyzyjnych linków
Gdy aplikacja otrzyma intencję, docelowa aktywność może być wyświetlana jako druga część podzielonej aktywności, np. prośba o wyświetlenie ekranu szczegółów z informacjami o elemencie z listy. Na małych wyświetlaczach szczegóły są widoczne w pełnym oknie zadań, a na większych urządzeniach – obok listy.
 
  Żądanie uruchomienia powinno być kierowane do głównej aktywności, a aktywność z docelowymi szczegółami powinna być uruchamiana w trybie podzielonego ekranu. System automatycznie wybiera odpowiedni sposób prezentacji – w układzie pionowym lub obok siebie – na podstawie dostępnej szerokości wyświetlacza.
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)); }
Miejsce docelowe linku precyzyjnego może być jedyną aktywnością, która powinna być dostępna dla użytkownika w stosie nawigacji wstecznej. Możesz chcieć uniknąć zamknięcia aktywności szczegółów i pozostawienia tylko aktywności głównej:


Zamiast tego możesz zakończyć obie czynności w tym samym czasie, używając atrybutu finishPrimaryWithSecondary:
<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
Zapoznaj się z sekcją Atrybuty konfiguracji.
Wiele działań w podzielonych kontenerach
Łączenie wielu aktywności w podzielonym kontenerze umożliwia użytkownikom dostęp do szczegółowych treści. Na przykład w przypadku podziału na listę i szczegóły użytkownik może przejść do sekcji ze szczegółami, ale zachować główną aktywność:
 
  
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)); } }
Aktywność podrzędna jest umieszczana nad aktywnością szczegółową, zasłaniając ją:

Użytkownik może następnie wrócić do poprzedniego poziomu szczegółowości, cofając się w stosie:
 
  Domyślnie działania są układane jeden na drugim, gdy są uruchamiane z działania w tym samym kontenerze dodatkowym. Aktywności uruchamiane z głównego kontenera w aktywnym podziale trafiają też do kontenera dodatkowego na górze stosu aktywności.
Działania w nowym zadaniu
Gdy działania w oknie podzielonego zadania uruchamiają działania w nowym zadaniu, nowe zadanie jest oddzielone od zadania, które obejmuje podział, i wyświetla się w pełnym oknie. Na ekranie Ostatnie widać 2 zadania: zadanie w podzielonym widoku i nowe zadanie.
 
  Zastępowanie aktywności
Aktywności można zastępować w stosie kontenera dodatkowego, np. gdy aktywność główna jest używana do nawigacji najwyższego poziomu, a aktywność dodatkowa jest wybranym miejscem docelowym. Każdy wybór z nawigacji najwyższego poziomu powinien rozpocząć nową aktywność w kontenerze dodatkowym i usunąć aktywność lub aktywności, które były w nim wcześniej.
 
  Jeśli aplikacja nie zakończy działania w kontenerze dodatkowym po zmianie wyboru nawigacji, nawigacja wsteczna może być myląca, gdy podział jest zwinięty (gdy urządzenie jest złożone). Jeśli na przykład w panelu głównym masz menu, a w panelu dodatkowym ekrany A i B, to po złożeniu telefonu ekran B będzie nad ekranem A, a ekran A nad menu. Gdy użytkownik wróci z B, zamiast menu pojawi się A.
W takich przypadkach ekran A musi zostać usunięty z listy wstecznej.

Domyślne działanie podczas uruchamiania z boku w nowym kontenerze na istniejącym podziale polega na umieszczeniu nowych kontenerów pomocniczych na górze i zachowaniu starych w stosie wstecznym. Możesz skonfigurować podziały tak, aby wyczyścić poprzednie kontenery pomocnicze za pomocą clearTop i normalnie uruchamiać nowe działania.
<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))); } }
Możesz też użyć tego samego działania dodatkowego i z działania głównego (menu) wysyłać nowe intencje, które będą rozwiązywane w tej samej instancji, ale będą wywoływać aktualizację stanu lub interfejsu w kontenerze dodatkowym.
Wiele podziałów
Aplikacje mogą zapewniać wielopoziomową nawigację, uruchamiając dodatkowe aktywności z boku.
Gdy działanie w kontenerze dodatkowym uruchamia nowe działanie z boku, nad istniejącym podziałem tworzony jest nowy podział.
 
  Stos wsteczny zawiera wszystkie wcześniej otwarte aktywności, dzięki czemu użytkownicy mogą po zakończeniu aktywności C przejść do podziału A/B.

Aby utworzyć nowy podział, uruchom nową aktywność z boku istniejącego kontenera dodatkowego. Zadeklaruj konfiguracje dla podziałów A/B i B/C i uruchom aktywność C w normalny sposób z aktywności 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)); } }
Reagowanie na zmiany stanu podziału
Różne działania w aplikacji mogą mieć elementy interfejsu, które pełnią tę samą funkcję, np. element sterujący otwierający okno z ustawieniami konta.
 
  Jeśli 2 aktywności, które mają wspólny element interfejsu, są podzielone, wyświetlanie tego elementu w obu aktywnościach jest zbędne i może być mylące.
 
  Aby wiedzieć, kiedy działania są w podziale, sprawdź przepływ SplitController.splitInfoList lub zarejestruj detektor za pomocą SplitControllerCallbackAdapter, aby monitorować zmiany stanu podziału. Następnie dostosuj interfejs:
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); }); }
Korutyny można uruchamiać w dowolnym stanie cyklu życia, ale zwykle uruchamia się je w stanie STARTED, aby oszczędzać zasoby (więcej informacji znajdziesz w artykule Używanie korutyn Kotlin z komponentami uwzględniającymi cykl życia).
Wywołania zwrotne mogą być wykonywane w dowolnym stanie cyklu życia, w tym gdy aktywność jest zatrzymana. Słuchacze powinni być zwykle zarejestrowani w onStart() i niezarejestrowani w onStop().
Okno modalne na pełnym ekranie
Niektóre aktywności blokują możliwość interakcji z aplikacją, dopóki nie zostanie wykonane określone działanie, np. ekran logowania, ekran potwierdzenia zasad lub komunikat o błędzie. Nie należy wyświetlać działań modalnych w podziale.
Aby aktywność zawsze wypełniała okno zadania, możesz użyć konfiguracji expand:
<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>
Kończenie aktywności
Użytkownicy mogą kończyć działania po obu stronach podziału, przesuwając palcem od krawędzi wyświetlacza:
 
   
  Jeśli urządzenie jest skonfigurowane tak, aby używać przycisku Wstecz zamiast nawigacji gestami, dane wejściowe są wysyłane do aktywnego działania – działania, które zostało dotknięte lub uruchomione jako ostatnie.
Wpływ ukończenia wszystkich aktywności w kontenerze na kontener przeciwny zależy od konfiguracji podziału.
Atrybuty konfiguracji
Możesz określić atrybuty reguły podziału, aby skonfigurować, jak zakończenie wszystkich działań po jednej stronie podziału wpływa na działania po drugiej stronie podziału. Atrybuty to:
- window:finishPrimaryWithSecondary– Jak ukończenie wszystkich działań w kontenerze dodatkowym wpływa na działania w kontenerze głównym
- window:finishSecondaryWithPrimary– Jak ukończenie wszystkich działań w kontenerze głównym wpływa na działania w kontenerze dodatkowym
Możliwe wartości atrybutów to:
- always– zawsze kończ działania w powiązanym kontenerze.
- never– nigdy nie kończ aktywności w powiązanym kontenerze.
- adjacent– dokończ działania w powiązanym kontenerze, gdy oba kontenery są wyświetlane obok siebie, ale nie wtedy, gdy są ułożone jeden na drugim.
Na przykład:
<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>
Konfiguracja domyślna
Gdy wszystkie działania w jednym kontenerze podziału zostaną zakończone, pozostały kontener zajmuje całe okno:
<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>


Wspólne wykonywanie zadań
Automatycznie kończ działania w kontenerze głównym, gdy wszystkie działania w kontenerze dodatkowym zostaną zakończone:
<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>


Automatycznie kończ działania w kontenerze dodatkowym, gdy wszystkie działania w kontenerze głównym zostaną zakończone:
<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>


Zakończ działania razem, gdy wszystkie działania w kontenerze głównym lub dodatkowym zostaną zakończone:
<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>


Kończenie wielu aktywności w kontenerach
Jeśli w podzielonym kontenerze jest kilka aktywności, zakończenie aktywności na dole stosu nie powoduje automatycznego zakończenia aktywności na górze.
Jeśli na przykład w kontenerze dodatkowym znajdują się 2 aktywności, C nad B:

Konfiguracja podziału jest określona przez konfigurację działań A i B:
<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>
zakończenie aktywności u góry zachowuje podział.

Zakończenie działania u dołu (u podstaw) kontenera dodatkowego nie powoduje usunięcia działań znajdujących się nad nim, a tym samym zachowuje podział.

Wykonywane są też wszelkie dodatkowe reguły dotyczące wspólnego kończenia aktywności, np. kończenia aktywności dodatkowej z aktywnością główną:
<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

A gdy podział jest skonfigurowany tak, aby zakończyć podstawowe i dodatkowe razem:
<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>



Zmienianie właściwości podziału w czasie działania
Nie można zmieniać właściwości aktywnego i widocznego podziału. Zmiana reguł podziału wpływa na dodatkowe uruchomienia aktywności i nowe kontenery, ale nie na istniejące i aktywne podziały.
Aby zmienić właściwości aktywnych podziałów, zakończ działanie lub działania poboczne w podziale i ponownie uruchom je z nową konfiguracją.
Dynamiczne właściwości podziału
Android 15 (poziom interfejsu API 35) i nowsze wersje obsługiwane przez Jetpack WindowManager 1.4 i nowsze oferują dynamiczne funkcje, które umożliwiają konfigurowanie podziałów osadzania aktywności, w tym:
- Rozwijanie okienka: interaktywny, przesuwalny separator umożliwia użytkownikom zmianę rozmiaru okienek w prezentacji podzielonej na części.
- Przypinanie stosu aktywności: użytkownicy mogą przypinać treści w jednym kontenerze i odseparowywać nawigację w tym kontenerze od nawigacji w innym kontenerze.
- Przyciemnienie okna dialogowego na pełnym ekranie: podczas wyświetlania okna dialogowego aplikacje mogą określić, czy przyciemnić całe okno zadania, czy tylko kontener, w którym zostało otwarte okno dialogowe.
Rozwijanie panelu
Rozszerzanie panelu umożliwia użytkownikom dostosowanie ilości miejsca na ekranie przydzielonego do dwóch aktywności w układzie dwupanelowym.
Aby dostosować wygląd separatora okien i ustawić zakres, w którym można go przesuwać:
- Tworzenie instancji - DividerAttributes
- Dostosuj atrybuty separatora: - color:kolor separatora panelu, który można przeciągać.
- widthDp: szerokość separatora panelu, który można przeciągać. Ustaw wartość- WIDTH_SYSTEM_DEFAULT, aby system określał szerokość separatora.
- Zakres przeciągania: minimalny odsetek ekranu, jaki może zajmować każdy z paneli. Może wynosić od 0,33 do 0,66. Ustaw wartość - DRAG_RANGE_SYSTEM_DEFAULT, aby system określał zakres przeciągania.
 - 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(); 
Przypinanie stosu aktywności
Przypinanie stosu aktywności umożliwia użytkownikom przypinanie jednego z podzielonych okien, dzięki czemu aktywność pozostaje bez zmian, gdy użytkownicy poruszają się w drugim oknie. Przypinanie stosu aktywności zapewnia lepszą wielozadaniowość.
Aby włączyć przypinanie stosu aktywności w aplikacji:
- Dodaj przycisk do pliku układu aktywności, którą chcesz przypiąć, np. do aktywności szczegółowej układu lista-szczegóły: - <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>
- W metodzie - onCreate()aktywności ustaw odbiornik kliknięć na przycisku:- 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); }); 
Okno pełnoekranowe – przyciemnienie
Aktywności zwykle przyciemniają wyświetlacze, aby zwrócić uwagę na okno. W przypadku osadzania aktywności oba panele wyświetlacza dwupanelowego powinny być przyciemnione, a nie tylko panel zawierający aktywność, która otworzyła okno dialogowe. Zapewni to spójny interfejs.
W przypadku WindowManager w wersji 1.4 i nowszych całe okno aplikacji jest domyślnie przyciemniane po otwarciu okna dialogowego (patrz EmbeddingConfiguration.DimAreaBehavior.ON_TASK).
Aby przyciemnić tylko kontener aktywności, która otworzyła okno, użyj elementu EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.
Wyodrębnianie aktywności z podzielonego okna do pełnego okna
Utwórz nową konfigurację, która wyświetla działanie boczne w pełnym oknie, a następnie ponownie uruchom działanie z intencją, która prowadzi do tego samego wystąpienia.
Sprawdzanie obsługi podziału w czasie działania
Osadzanie aktywności jest obsługiwane na Androidzie 12L (poziom interfejsu API 32) i nowszym, ale jest też dostępne na niektórych urządzeniach z wcześniejszymi wersjami platformy. Aby sprawdzić dostępność funkcji w czasie działania programu, użyj właściwości SplitController.splitSupportStatus lub metody 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. }
Jeśli podział ekranu nie jest obsługiwany, aktywności są uruchamiane na stosie aktywności (zgodnie z modelem osadzania aktywności).
Zapobieganie zastąpieniu przez system
Producenci urządzeń z Androidem (producenci oryginalnego sprzętu, czyli OEM) mogą zaimplementować osadzanie aktywności jako funkcję systemu urządzenia. System określa reguły podziału dla aplikacji z wieloma aktywnościami, zastępując zachowanie okien aplikacji. Zastąpienie przez system wymusza na aplikacjach z wieloma aktywnościami tryb osadzania aktywności zdefiniowany przez system.
Osadzanie aktywności systemowej może poprawić prezentację aplikacji dzięki układom wielopanelowym, takim jak lista-szczegóły, bez wprowadzania zmian w aplikacji. Osadzanie aktywności systemowej może jednak powodować nieprawidłowe układy aplikacji, błędy lub konflikty z osadzaniem aktywności zaimplementowanym przez aplikację.
Aplikacja może zapobiegać osadzaniu aktywności systemowej lub na to zezwalać, ustawiając wartość PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE w pliku manifestu aplikacji, np.:
<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>
Nazwa właściwości jest zdefiniowana w obiekcie Jetpack WindowManager WindowProperties. Ustaw wartość false, jeśli aplikacja implementuje osadzanie aktywności lub jeśli chcesz w inny sposób uniemożliwić systemowi stosowanie w niej reguł osadzania aktywności. Ustaw wartość true, aby zezwolić systemowi na stosowanie w aplikacji zdefiniowanego przez niego osadzania aktywności.
Ograniczenia i ostrzeżenia
- Tylko aplikacja hostująca zadanie, która jest identyfikowana jako właściciel głównej aktywności w zadaniu, może organizować i osadzać inne aktywności w zadaniu. Jeśli działania obsługujące osadzanie i podział są uruchamiane w zadaniu należącym do innej aplikacji, osadzanie i podział nie będą działać w przypadku tych działań.
- Działania można organizować tylko w ramach jednego zadania. Uruchomienie aktywności w nowym zadaniu zawsze powoduje otwarcie jej w nowym, rozwiniętym oknie poza istniejącymi podziałami.
- Dzielić można tylko działania w ramach tego samego procesu. Wywołanie zwrotne SplitInfozgłasza tylko działania należące do tego samego procesu, ponieważ nie ma możliwości uzyskania informacji o działaniach w innych procesach.
- Każda reguła dotycząca pary lub pojedynczej aktywności ma zastosowanie tylko do uruchomień aktywności, które nastąpiły po zarejestrowaniu reguły. Obecnie nie ma możliwości aktualizowania istniejących podziałów ani ich właściwości wizualnych.
- Konfiguracja filtra pary podziału musi być zgodna z intencjami używanymi podczas pełnego uruchamiania aktywności. Dopasowanie następuje w momencie, gdy w procesie aplikacji rozpoczyna się nowa aktywność, więc może nie znać nazw komponentów, które są rozwiązywane później w procesie systemowym podczas korzystania z niejawnych intencji. Jeśli nazwa komponentu nie jest znana w momencie uruchomienia, można zamiast niej użyć symbolu wieloznacznego („*/*”), a filtrowanie można przeprowadzić na podstawie działania intencji.
- Obecnie nie można przenosić aktywności między kontenerami ani do podziałów i z nich po ich utworzeniu. Podziały są tworzone tylko przez bibliotekę WindowManager, gdy uruchamiane są nowe aktywności z pasującymi regułami, a usuwane, gdy kończy się ostatnia aktywność w kontenerze podziału.
- Działania można ponownie uruchomić, gdy zmieni się konfiguracja, więc gdy podział zostanie utworzony lub usunięty, a granice działania ulegną zmianie, działanie może zostać całkowicie zniszczone i utworzone na nowo. W związku z tym deweloperzy aplikacji powinni zachować ostrożność w przypadku takich działań jak uruchamianie nowych aktywności z wywołań zwrotnych cyklu życia.
- Urządzenia muszą zawierać interfejs rozszerzeń okien, aby obsługiwać osadzanie aktywności. Interfejs jest dostępny na prawie wszystkich urządzeniach z dużym ekranem z Androidem 12L (API na poziomie 32) lub nowszym. Niektóre urządzenia z dużym ekranem, które nie obsługują wielu aktywności, nie mają jednak interfejsu rozszerzeń okien. Jeśli urządzenie z dużym ekranem nie obsługuje trybu wielu okien, może nie obsługiwać osadzania aktywności.
Dodatkowe materiały
- Codelabs:
- Ścieżka szkoleniowa – osadzanie aktywności
- Przykładowa aplikacja – activity-embedding
