Umieszczanie aktywności

Umieszczanie aktywności optymalizuje działanie aplikacji na urządzeniach z dużym ekranem, dzieląc okno zadania aplikacji między 2 aktywności lub 2 instancje tej samej aktywności.

Rysunek 1. Aplikacja Ustawienia z aktywnościami wyświetlanymi obok siebie.

Jeśli Twoja aplikacja składa się z kilku aktywności, jej osadzenie umożliwia lepsze wrażenia użytkownika na tabletach, składanych urządzeniach i urządzeniach z ChromeOS.

Wstawianie aktywności nie wymaga refaktoryzacji kodu. Sposób wyświetlania aktywności przez aplikację (obok siebie lub ułożone w stosie) możesz określić, tworząc plik konfiguracji XML lub wykonując wywołania interfejsu Jetpack WindowManager.

Obsługa małych ekranów jest automatyczna. Gdy aplikacja jest 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 prezentację na podstawie utworzonej przez Ciebie konfiguracji – nie jest wymagana żadna logika rozgałęzienia.

Wstawianie aktywności dostosowuje się do zmian orientacji urządzenia i działa bezproblemowo na składanych urządzeniach, układając i rozkładają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.

Okno podzielonego zadania

Przekazywanie aktywności dzieli okno zadania aplikacji na 2 kontenery: główny i dodatkowy. Kontenery zawierają działania uruchamiane z głównego działania lub z innych działań znajdujących się już w kontenerach.

Aktywności są umieszczane w kontenerze dodatkowym w miarę ich uruchamiania, a na małych ekranach kontener dodatkowy jest umieszczany na wierzchu kontenera głównego. Dzięki temu aktywność i powrót do poprzedniego ekranu są zgodne z kolejnością aktywności już wbudowanych w aplikację.

Umieszczenie w ramach 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:

Rysunek 2. 2 aktywności obok siebie.

Aktywność, która zajmuje całe okno zadania, może utworzyć podział, uruchamiając nową aktywność obok:

Rysunek 3. Aktywność A uruchamia aktywność B na boku.

Aktywności, które są już w ramach podziału i dzielą okno zadania, mogą uruchamiać inne aktywności na te sposoby:

  • Po bokach innej aktywności:

    Rysunek 4. Aktywność A uruchamia aktywność C po stronie aktywności B.
  • Na bok, aby przesunąć podział na bok i ukryć poprzednią główną aktywność:

    Rysunek 5. Aktywność B rozpoczyna się z poziomu aktywności C i przesuwa podział w bok.
  • uruchomić aktywność na pierwszym miejscu, czyli na tym samym stosie aktywności:

    Rysunek 6. Aktywność B uruchamia aktywność C bez dodatkowych flag intencji.
  • Uruchomienie pełnoekranowego okna aktywności w tym samym zadaniu:

    Rysunek 7. Aktywność A lub aktywność B uruchamia aktywność C, która wypełnia okno zadania.

Nawigacja wstecz

Różne typy aplikacji mogą mieć różne reguły przechodzenia do poprzedniego stanu w oknie podzielonego zadania w zależności od zależności między aktywnościami lub sposobu wywołania przez użytkowników zdarzenia wstecz, na przykład:

  • W powiązaniu: jeśli aktywności są powiązane i jedna nie powinna być wyświetlana bez drugiej, możesz skonfigurować przekierowanie wstecz, aby zakończyć obie.
  • Samodzielne działanie: jeśli czynności są całkowicie niezależne, cofnięcie się z jednej czynności nie wpływa na stan innej czynności w oknie zadania.

Gdy korzystasz z przemieszczania za pomocą przycisków, zdarzenie wstecz jest wysyłane do ostatniej aktywnej czynności.

W przypadku nawigacji przy użyciu gestów:

  • Android 14 (poziom interfejsu API 34) i starsze – zdarzenie wstecz jest wysyłane do aktywności, w której wystąpił gest. Gdy użytkownik przesunie palcem po lewej stronie ekranu, zdarzenie wstecz zostanie wysłane do aktywności w lewym panelu podzielonego okna. Gdy użytkownicy przesuwają palcem po prawej stronie ekranu, zdarzenie wstecz jest wysyłane do aktywności w panelu po prawej stronie.

  • Android 15 (poziom 35 interfejsu API) lub nowszy

    • W przypadku wielu działań z tej samej aplikacji gest kończy główne działanie niezależnie od kierunku przesunięcia, co zapewnia bardziej spójne działanie.

    • W scenariuszach obejmujących 2 aktywności z różnych aplikacji (nakładka) zdarzenie wstecz jest kierowane do ostatniej aktywnej aktywności, zgodnie z zachowaniem nawigacji przy użyciu przycisku.

Układ z wieloma panelami

Jetpack WindowManager umożliwia tworzenie aktywności z przewijanym układem na urządzeniach z dużym ekranem z Androidem 12L (poziom interfejsu API 32) lub nowszym oraz na niektórych urządzeniach z wersjami platformy starszymi niż ta wersja. Istniejące aplikacje, które są oparte na wielu działaniach, a nie na fragmentach lub układach opartych na widoku, takich jak SlidingPaneLayout, mogą zapewnić lepsze wrażenia użytkownika na dużym ekranie bez refaktoryzacji kodu źródłowego.

Typowym przykładem 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ść czeka, aż oba zajęcia zostaną narysowane, a potem wyświetla je razem. Z punktu widzenia użytkownika obie te czynności są wykonywane jednocześnie.

Rysunek 8. 2 aktywności uruchomione jednocześnie w układzie z wieloma panelami.

Atrybuty podziału

Możesz określić, jak proporcjonalnie rozdzielić okno zadania między podzielonymi kontenerami oraz jak ułożyć kontenery względem siebie.

W przypadku reguł zdefiniowanych w pliku konfiguracji XML ustaw te atrybuty:

  • splitRatio: określa proporcje kontenera. Wartość jest liczbą zmiennoprzecinkową z otwartego przedziału (0,0, 1,0).
  • splitLayoutDirection: określa, jak rozbite kontenery są rozmieszczone względem siebie. Dostępne wartości:
    • ltr: od lewej do prawej
    • rtl: od prawej do lewej
    • locale: wartość ltr lub rtl jest określana na podstawie ustawienia lokalizacji.

Przykłady znajdziesz w sekcji Konfiguracja XML.

W przypadku reguł utworzonych za pomocą interfejsów WindowManager API utwórz obiekt SplitAttributes za pomocą obiektu SplitAttributes.Builder i wywołaj te metody konstruktora:

Przykłady znajdziesz w sekcji WindowManager API.

Rysunek 9. 2 podziały aktywności ułożone od lewej do prawej, ale z różnymi współczynnikami podziału

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 w ramach układu listy z szczegółami element zastępczy może zajmować część drugorzędną w ramach podziału aktywności, dopóki nie wybierzesz elementu z listy. Wtedy element zawierający szczegółowe informacje o wybranym elemencie listy zastąpi element zastępczy.

Domyślnie system wyświetla zastępniki tylko wtedy, gdy jest wystarczająco dużo miejsca na podział aktywności. Zasoby zastępcze automatycznie się kończą, gdy rozmiar wyświetlania zmienia się na taki, który jest za wąski lub za krótki, aby wyświetlić podział. Jeśli jest na to miejsce, system ponownie uruchamia placeholder z ponownym zainicjalizowanym stanem.

Rysunek 10. Składanie i rozkładanie składanego urządzenia Aktywność zastępcza jest kończyna i powstaje ponownie, gdy zmienia się rozmiar wyświetlania.

Jednak atrybut stickyPlaceholder metody SplitPlaceholderRule lub setSticky() interfejsu SplitPlaceholder.Builder może zastąpić domyślne działanie. Gdy atrybut lub metoda określa wartość true, system wyświetla obiekt zastępczy jako najwyższą aktywność w oknie zadania, gdy rozmiar wyświetlania zostanie zmniejszony z dwu paneli do jednego (patrz Konfiguracja podziału).

Rysunek 11. Składanie i rozkładanie składanego urządzenia Zakładka placeholdera aktywność jest przyklejona.

Zmiana rozmiaru okna

Gdy zmiany w konfiguracji urządzenia zmniejszają szerokość okna zadania, tak że nie mieści się ono w układzie z wieloma panelami (np. gdy składany tablet zmienia rozmiar z rozmiaru tabletu na rozmiar telefonu lub gdy zmienia się rozmiar okna aplikacji w trybie wielopanelowym), czynności, które nie są elementami zastępczymi, w panelu pomocniczym okna zadania są ułożone na czynnościach w panelu głównym.

Aktywności zastępcze są wyświetlane tylko wtedy, gdy jest wystarczająca szerokość wyświetlania na podział. Na mniejszych ekranach substytut jest automatycznie usuwany. Gdy obszar wyświetlania znów stanie się wystarczająco duży, placeholder zostanie utworzony ponownie. (patrz sekcja Znaki zastępcze).

Nakładanie się aktywności jest możliwe, ponieważ WindowManager sortuje aktywności w panelu pomocniczym nad aktywnościami w panelu głównym.

Wiele działań w panelu dodatkowym

Aktywność B uruchamia aktywność C bez dodatkowych flag intencji:

Podział aktywności zawierający aktywności A, B i C, przy czym aktywność C jest nałożona na aktywność B.

w efekcie w ramach tego samego zadania występuje następująca kolejność działań:

Użytkownik ma 2 stosy aktywności: pierwszy zawiera aktywność C, która jest ułożona na aktywność B.
          Drugi stos jest ułożony na stosie aktywności podstawowej zawierającym aktywność A.

W mniejszym oknie zadania aplikacja zwija się do pojedynczej aktywności z C na szczycie stosu:

Małe okno pokazujące tylko aktywność C.

W mniejszym oknie możesz przełączać się między aktywnościami ułożonymi w wielu warstwach.

Jeśli konfiguracja okna zadania zostanie przywrócona do większego rozmiaru, który może pomieścić kilka paneli, aktywności będą ponownie wyświetlane obok siebie.

Warstwowe podziały

Aktywność B rozpoczyna się z poziomego położenia aktywności C i przesuwa podział w bok:

Okno zadania pokazujące czynności A i B, a potem czynności B i C.

W efekcie w ramach tego samego zadania czynności są wykonywane w takiej kolejności:

Działania A, B i C w jednym stosie. Aktywności są ułożone w kolejności od góry do dołu: C, B, A.

W mniejszym oknie zadania aplikacja zwija się do pojedynczej czynności z literą C u góry:

Małe okno pokazujące tylko aktywność C.

Orientacja pionowa

Ustawienie w pliku manifestu android:screenOrientation umożliwia aplikacjom ograniczenie aktywności do orientacji pionowej lub poziomej. Aby zwiększyć wygodę użytkowników na urządzeniach z dużym ekranem, takich jak tablety i telefony składane, producenci urządzeń (OEM) mogą ignorować prośby dotyczące orientacji ekranu i umieszczać aplikację w orientacji pionowej na ekranach poziomych lub w orientacji poziomej na ekranach pionowych.

Rysunek 12. Aktywności w formacie letterbox: format pionowy na urządzeniu w orientacji poziomej (po lewej) i format poziomy na urządzeniu w orientacji pionowej (po prawej).

Podobnie, gdy włączone jest umieszczanie w ramce, producenci urządzeń mogą dostosować urządzenia do wyświetlania aktywności w ramce letterbox w orientacji pionowej na dużych ekranach (szerokość ≥ 600 dp). Gdy działanie w układzie pionowym uruchamia drugie działanie, urządzenie może wyświetlać oba działania obok siebie na ekranie podzielonym na 2 części.

Rysunek 13. Aktywność A w układzie pionowym uruchamia aktywność B na boku.

Zawsze dodawaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED do pliku manifestu aplikacji, aby informować urządzenia o tym, że aplikacja obsługuje umieszczanie aktywności (patrz sekcja Konfiguracja podziału). Urządzenia spersonalizowane przez producenta mogą następnie określić, czy mają wyświetlać aktywność w formacie letterbox.

Konfiguracja podziału

Reguły podziału konfigurują podział aktywności. Reguły podziału definiujesz w pliku konfiguracyjnym XML lub za pomocą wywołań interfejsu Jetpack WindowManager API.

W obu przypadkach aplikacja musi mieć dostęp do biblioteki WindowManager i musi poinformować system, że zaimplementowała wbudowaną aktywność.

Wykonaj te czynności:

  1. Dodaj najnowszą zależność biblioteki WindowManager do pliku build.gradle na poziomie modułu aplikacji, na przykład:

    implementation 'androidx.window:window:1.1.0-beta02'

    Biblioteka WindowManager udostępnia wszystkie komponenty wymagane do umieszczania aktywności.

  2. Poinformuj system, że Twoja aplikacja ma funkcję wklejania aktywności.

    Dodaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED do elementu <application> w pliku manifestu aplikacji i ustaw jego 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 wersji WindowManager 1.1.0-alpha06 i nowszych funkcja podziału wstawiania aktywności jest wyłączona, chyba że doda się do pliku manifestu odpowiednią właściwość i ustawi się ją na wartość true.

    Producenci urządzeń używają tego ustawienia, aby włączyć funkcje niestandardowe w przypadku aplikacji, które obsługują umieszczanie aktywności. Na przykład urządzenia mogą wyświetlać w układce letterbox aktywność w orientacji pionowej na ekranach w orientacji poziomej, aby po rozpoczęciu drugiej aktywności przejść do układu z 2 panelami (patrz Stały układ pionowy).

Konfiguracja XML

Aby utworzyć implementację umieszczania aktywności na podstawie pliku XML, wykonaj te czynności:

  1. Utwórz plik zasobu XML, który:

    • Określa aktywności, które mają wspólny podział
    • Konfigurowanie opcji podziału
    • Tworzy zastępczy element dla dodatkowego kontenera podziału, gdy treści są niedostępne.
    • Określa działania, które nigdy nie powinny być uwzględniane w podziale

    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>
    
  2. Utwórz funkcję inicjalizacyjną.

    Komponent WindowManager RuleController analizuje plik konfiguracji XML i udostępnia reguły systemowi. Biblioteka Jetpacka Startup Initializer udostępnia plik XML bibliotece RuleController podczas uruchamiania aplikacji, dzięki czemu reguły są aktywne, gdy rozpoczyna się jakakolwiek aktywność.

    Aby utworzyć funkcję inicjalizacyjną:

    1. Dodaj najnowszą zależność biblioteki Jetpack Startup do pliku na poziomie modułubuild.gradle, na przykład:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. Utwórz klasę, która implementuje interfejs Initializer.

      Inicjalizator 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();
           }
      }
  3. Utwórz dostawcę treści dla definicji reguł.

    Dodaj androidx.startup.InitializationProvider do pliku manifestu aplikacji jako <provider>. Dołącz odwołanie do implementacji inicjalizatora 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>
    

    InitializationProvider wykrywa i inicjalizuje SplitInitializer przed wywołaniem metody onCreate() aplikacji. W rezultacie reguły podziału są aktywne, gdy rozpoczyna się główna aktywność aplikacji.

Interfejs WindowManager API

Umieszczenie aktywności można zaimplementować programowo za pomocą kilku wywołań interfejsu API. Wykonuj wywołania w metodzie onCreate() podklasy Application, aby upewnić się, że reguły są aktywne przed uruchomieniem aktywności.

Application

Aby utworzyć podział aktywności za pomocą kodu:

  1. Utwórz regułę podziału:

    1. Utwórz SplitPairFilter, który identyfikuje działania, 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
      );
    2. Dodaj filtr do zestawu filtrów:

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
    3. 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

      final SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();

      SplitAttributes.Builder tworzy obiekt zawierający atrybuty układu:

      • setSplitType(): określa sposób przydzielania dostępnej powierzchni wyświetlania poszczególnym kontenerom aktywności. Typ podziału na podstawie proporcji określa proporcję dostępnej powierzchni wyświetlania przypisanej do głównego kontenera. Pozostała część dostępnej powierzchni wyświetlania jest przypisana do kontenera dodatkowego.
      • setLayoutDirection(): określa sposób rozmieszczenia kontenerów aktywności względem siebie (najpierw kontener główny).
    4. Kompilacja 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 tworzy i konfiguruje regułę:

      • filterSet: zawiera filtry par podziałów, które określają, kiedy stosować regułę, przez identyfikowanie działań, które mają wspólny podział.
      • 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(): określa 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(): określa maksymalny współczynnik proporcji wyświetlania (wysokość:szerokość) w orientacji pionowej, w której wyświetlane są podziały aktywności. Jeśli współczynnik proporcji w układem pionowym przekracza maksymalny współczynnik proporcji, podziały są wyłączone niezależnie od szerokości wyświetlacza. Uwaga: domyślna wartość to 1,4, co powoduje, że na większości tabletów aktywności zajmują całe okno zadania w orientacji pionowej. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT i setMaxAspectRatioInLandscape(). Wartością domyślną dla krajobrazu jest ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary(): określa sposób, w jaki zakończenie wszystkich aktywności w kontenerze pomocniczym wpływa na aktywności w kontenerze głównym. NEVER oznacza, że system nie powinien kończyć działań głównych, gdy wszystkie działania w kontenerze podrzędnym 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. ALWAYS wskazuje, że system powinien zawsze kończyć działania w kontenerze pomocniczym, gdy wszystkie działania w kontenerze głównym zostaną zakończone (patrz Kończenie działań).
      • setClearTop(): określa, czy wszystkie aktywności w kontenerze dodatkowym są kończyć się, gdy w kontenerze uruchamia się nowa aktywność. Wartość false określa, że nowe aktywności są nakładane na aktywności, które są już w kontenerze pomocniczym.
    5. Pobierz pojedynczy egzemplarz klasy WindowManager RuleController i dodaj regułę:

      Kotlin

        val ruleController = RuleController.getInstance(this)
        ruleController.addRule(splitPairRule)
        

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. Gdy treści nie są dostępne, utwórz zastępcze dla kontenera dodatkowego:

    1. Utwórz ActivityFilter, który identyfikuje aktywność, z którą placeholder dzieli okno zadania:

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. Dodaj filtr do zestawu filtrów:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
    3. 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(context, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();

      SplitPlaceholderRule.Builder tworzy i konfiguruje regułę:

      • placeholderActivityFilterSet: zawiera filtry aktywności, które określają, kiedy należy zastosować regułę, przez identyfikowanie aktywności, z którymi powiązana jest aktywność zastępcza.
      • Intent: określa uruchomienie aktywności obiektu zastępczego.
      • setDefaultSplitAttributes(): atrybuty układu są stosowane do reguły.
      • setMinWidthDp(): Ustawia minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości) umożliwiającą podział.
      • setMinSmallestWidthDp(): zmienia 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, w której wyświetlane są podziały aktywności. Uwaga: wartość domyślna to 1,4, co powoduje, że na większości tabletów aktywności wypełniają okno zadania w orientacji poziomej. Zapoznaj się też z SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape(). Wartością domyślną dla orientacji poziomej jest ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder(): określa, jak zakończenie działania obiektu zastępczego wpływa na działania w kontenerze głównym. ZAWSZE wskazuje, że system zawsze powinien zakończyć czynności w kontenerze głównym po zakończeniu działania placeholdera (patrz Zakończ czynności).
      • setSticky(): określa, czy aktywność zastępcza ma być widoczna na szczycie stosu aktywności na małych ekranach, gdy zastępcza aktywność zostanie po raz pierwszy wyświetlona w ramach podziału z wystarczającą minimalną szerokością.
    4. Dodaj regułę do WindowManager RuleController:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. Określ działania, które nigdy nie powinny być uwzględniane w podziale:

    1. Utwórz ActivityFilter, który identyfikuje aktywność, która powinna zawsze zajmować całą powierzchnię wyświetlania zadania:

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. Dodaj filtr do zestawu filtrów:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. Utwórz ActivityRule:

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();

      ActivityRule.Builder tworzy i konfiguruje regułę:

      • expandedActivityFilterSet: zawiera filtry aktywności, które określają, kiedy należy zastosować regułę, przez identyfikowanie aktywności, które chcesz wykluczyć z podziału.
      • setAlwaysExpand(): określa, czy aktywność ma wypełniać całe okno zadania.
    4. Dodaj regułę do WindowManager RuleController:

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

Umieszczenie wewnątrz innych aplikacji

W Androidzie 13 (API na poziomie 33) i nowszych aplikacje mogą umieszczać w sobie czynności z innych aplikacji. Umieszczenie aktywności w wielu aplikacjach (lub w wielu przypadkach UID) umożliwia wizualną integrację aktywności z wielu aplikacji na Androida. System wyświetla aktywność aplikacji hosta i osadzonej aktywności z innej aplikacji obok siebie lub u góry i u dołu, tak jak w przypadku osadzenia aktywności z jednej aplikacji.

Na przykład aplikacja Ustawienia może zawierać aktywność selektora tapety z aplikacji WallpaperPicker:

Rysunek 14. Aplikacja Ustawienia (menu po lewej stronie) z selektorem tapety jako wbudowaną aktywnością (po prawej).

Model zaufania

Procesy hosta, które umieszczają w ramach aktywności z innych aplikacji, mogą zmienić sposób wyświetlania tych aktywności, w tym ich rozmiar, położenie, przycięcie i przezroczystość. Złośliwi gospodarze mogą wykorzystać tę funkcję, aby wprowadzać użytkowników w błąd i tworzyć ataki typu clickjacking lub inne ataki polegające na przekierowywaniu użytkowników do fałszywych stron.

Aby zapobiec nadużyciom umieszczania treści w innych aplikacjach, Android wymaga, aby aplikacje zezwalały na umieszczanie treści z ich aktywności. Aplikacje mogą oznaczać hosty jako zaufane lub niezaufajne.

Zaufane hosty

Aby umożliwić innym aplikacjom umieszczanie i pełną kontrolę nad prezentacją działań z Twojej aplikacji, określ certyfikat SHA-256 aplikacji hosta w atrybucie android:knownActivityEmbeddingCerts elementu <activity> lub <application> w pliku manifestu aplikacji.

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 w ten sposób:

<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 rozdzielających. Więcej informacji znajdziesz w artykułach Tworzenie raportu z podpisywaniaUwierzytelnianie klienta.

Niezaufane hosty

Aby umożliwić każdej aplikacji umieszczanie działań z Twojej aplikacji i sterowanie ich wyświetlaniem, określ atrybut android:allowUntrustedActivityEmbedding w elementach <activity> lub <application> w pliku manifestu aplikacji, na przykład:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

Domyślna wartość atrybutu to „fałsz”, co uniemożliwia umieszczanie w innych aplikacjach.

Uwierzytelnianie niestandardowe

Aby ograniczyć ryzyko nieautoryzowanego umieszczania treś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 uwierzytelnienia. Jeśli gospodarz uwierzytelnił się po umieszczeniu Twojej aktywności, możesz wyświetlić rzeczywiste treści. Jeśli nie, możesz poinformować użytkownika, że działanie nie zostało dozwolone, i zablokować treści.

Użyj metody ActivityEmbeddingController#isActivityEmbedded() z biblioteki Jetpack WindowManager, aby sprawdzić, czy gospodarz umieszcza Twoją aktywność, na przykład:

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

Minimalny rozmiar

System Androida stosuje minimalną wysokość i szerokość określone w pliku manifestu aplikacji <layout> do zaimplementowanych działań. Jeśli aplikacja nie określa minimalnej wysokości i szerokości, obowiązują wartości domyślne systemu (sw220dp).

Jeśli host spróbuje zmienić rozmiar osadzonego kontenera na mniejszy niż minimalny, kontener zostanie rozszerzony, aby wypełnić cały obszar zadania.

<activity-alias>

Aby umożliwić umieszczanie zaufanych i niezaufanych działań w elementach <activity-alias>, należy zastosować element android:knownActivityEmbeddingCerts lub android:allowUntrustedActivityEmbedding do docelowego działania, a nie do aliasu. Zasada, która weryfikuje bezpieczeństwo na serwerze systemowym, jest określana na podstawie flag ustawionych na obiekcie docelowym, a nie na podstawie aliasu.

Aplikacja hosta

Aplikacje hosta implementują umieszczanie aktywności w innej aplikacji w taki sam sposób, w jaki implementują umieszczanie aktywności w jednej aplikacji. Obiekty SplitPairRule i SplitPairFilter lub ActivityRule i ActivityFilter określają wbudowane aktywności i podziały okna zadania. Reguły podziału są definiowane statycznie w pliku XML lub w czasie działania za pomocą wywołań interfejsu Jetpack WindowManager API.

Jeśli aplikacja hosta próbuje osadzić aktywność, która nie jest włączona do umieszczania w innych aplikacjach, aktywność zajmuje cały zakres zadania. W związku z tym aplikacje hosta muszą wiedzieć, czy docelowe działania umożliwiają umieszczanie elementów w innych aplikacjach.

Jeśli wbudowana aktywność uruchamia nową aktywność w tym samym zadaniu, a nowa aktywność nie została włączona w ramach wbudowanego kontenera, zajmuje ona cały obszar zadania, a nie nakłada się na aktywność wbudowanego kontenera.

Aplikacja hosta może umieszczać własne działania bez ograniczeń, o ile działania te są uruchamiane w ramach tego samego zadania.

Przykłady podziału

Podziel z pełnego okna

Rysunek 15. Aktywność A uruchamia aktywność B na boku.

Nie trzeba przeprowadzać refaktoryzacji. Konfigurację podziału możesz zdefiniować statycznie lub w czasie wykonywania, a potem wywołać funkcję Context#startActivity() bez żadnych dodatkowych parametrów.

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Domyślne dzielenie

Jeśli strona docelowa aplikacji jest podzielona na 2 kontenery na dużych ekranach, najlepiej jest tworzyć i prezentować jednocześnie obie aktywności. Jednak treści mogą być niedostępne dla kontenera pomocniczego w ramach podziału, dopóki użytkownik nie wejdzie w interakcję z aktywizacją w kontenerze głównym (np. nie wybierze elementu z menu nawigacyjnego). Aktywność obiektu zastępczego może wypełnić lukę do czasu, aż treści będą mogły być wyświetlane w drugim kontenerze w ramach podziału (patrz sekcja Obiekty zastępcze).

Rysunek 16. Rozdzielenie utworzone przez otwarcie 2 działań jednocześnie. Jedna aktywność jest placeholderem.

Aby utworzyć podział z zastępczym elementem, utwórz zastępczy element i połącz go z głównym działaniem:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

Gdy aplikacja otrzyma intencję, docelową aktywność można wyświetlić jako część wtórną podzielonej aktywności, na przykład prośbę o wyświetlenie ekranu z informacjami o produkcie z listy. Na małych ekranach szczegóły są wyświetlane w pełnym oknie zadania, a na większych urządzeniach – obok listy.

Rysunek 17. Aktywność szczegółowa precyzyjnego linku wyświetlana samodzielnie na małym ekranie, a także razem z aktywizacją listy na dużym ekranie.

Żądanie uruchomienia powinno być kierowane do głównej aktywności, a docelowy proces szczegółów powinien być uruchamiany w ramach podziału. System automatycznie wybierze odpowiednią prezentację – obok siebie lub w rzędzie – na podstawie dostępnej szerokości wyświetlacza.

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    . . .
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

Miejsce docelowe linku głębokiego może być jedyną aktywnością, która powinna być dostępna dla użytkownika w stosie nawigacji wstecz. Możesz też uniknąć usuwania aktywności z informacjami i zostawić tylko główną aktywność:

Duży ekran z wyświetlaną obok siebie listą aktywności i szczegółami.
          Powrót wstecz nie powoduje zamknięcia szczegółów i nie pozostawia aktywności na liście.

Mały wyświetlacz z tylko szczegółową aktywnością. Powrót wstecz nie może zamknąć aktywności na stronie ze szczegółami i ujawnić aktywności na liście.

Zamiast tego możesz zakończyć obie aktywności jednocześnie, używając atrybutu finishPrimaryWithSecondary:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

Zobacz sekcję Atrybuty konfiguracji.

Wiele działań w podzielonych kontenerach

Umieszczanie wielu aktywności w udzielnym kontenerze umożliwia użytkownikom dostęp do treści. Na przykład w przypadku podziału na listę i szczegóły użytkownik może przejść do sekcji podrzędnej, ale zachować główną aktywność:

Rysunek 18. Aktywność otwarta w miejscu w panelu pomocniczym okna zadania.

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

Aktywność podrzędna jest umieszczana na górze aktywności szczegółowej, co powoduje jej ukrycie:

Użytkownik może wrócić do poprzedniego poziomu szczegółów, przechodząc wstecz przez poziomy:

Rysunek 19. Aktywność została usunięta z góry grupy.

Domyślnym zachowaniem jest nakładanie się aktywności na siebie, gdy są one uruchamiane z aktywności w tym samym kontenerze pomocniczym. Działania uruchomione z głównego kontenera w ramach aktywnego podziału trafiają też do kontenera dodatkowego i znajdują się na szczycie stosu działań.

Aktywności w nowym zadaniu

Gdy czynności w oknie podzielonego zadania rozpoczną czynności w nowym zadaniu, nowe zadanie jest oddzielone od zadania, które zawiera podział, i wyświetlane w pełnym oknie. Na ekranie Ostatnie są widoczne 2 zadania: zadanie w ramach podziału i nowe zadanie.

Rysunek 20. Rozpocznij aktywność C w ramach nowego zadania z aktywności B.

Wymiana aktywności

Aktywności można zastępować w dodatkowym zbiorze kontenerów, np. gdy główna aktywność jest używana do nawigacji na najwyższym poziomie, a dodatkowa aktywność jest wybranym celem. Każdy element w menu najwyższego poziomu powinien uruchamiać nową aktywność w kontenerze drugorzędnym i usuwać aktywność lub aktywności, które były tam wcześniej.

Rysunek 21. Aktywność nawigacji najwyższego poziomu w panelu głównym zastępuje w panelu dodatkowym działania związane z miejscem docelowym.

Jeśli aplikacja nie zakończy aktywności w kontenerze drugorzędnym po zmianie wyboru nawigacji, cofanie może być mylące, gdy podział jest zwinięty (gdy urządzenie jest złożone). Jeśli np. w panelu głównym masz menu, a w panelu dodatkowym ekrany A i B, gdy użytkownik złoży telefon, ekran B znajdzie się nad ekranem A, a ekran A nad menu. Gdy użytkownik wraca z poziomu B, zamiast menu pojawia się poziom A.

W takich przypadkach ekran A musi zostać usunięty ze stosu.

Domyślne zachowanie podczas uruchamiania nowego kontenera na nowej karcie nad istniejącym podziałem polega na umieszczeniu nowych kontenerów pomocniczych u góry i zachowaniu starych na dolnym poziomie. Możesz skonfigurować podziały tak, aby czyś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

class MenuActivity {
    . . .
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity {
    . . .
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

Możesz też użyć tej samej aktywności pomocniczej, a z głównej aktywności (menu) wysyłać nowe intencje, które będą kierować do tego samego wystąpienia, ale będą uruchamiać stan lub aktualizację interfejsu w kontenerze pomocniczym.

Wiele podziałów

Aplikacje mogą zapewniać wielopoziomową nawigację, uruchamiając dodatkowe działania po bokach.

Gdy aktywność w kontenerze drugorzędnym uruchamia nową aktywność po stronie, nowy podział jest tworzony na wierzchu dotychczasowego podziału.

Rysunek 22. Aktywność B uruchamia z boku aktywność C.

Grupa wstecz zawiera wszystkie wcześniej otwarte aktywności, dzięki czemu użytkownicy mogą przejść do testu A/B po zakończeniu sekcji C.

Działania A, B i C w stosie. Aktywności są ułożone w kolejności od góry do dołu: C, B, A.

Aby utworzyć nowy podział, uruchom nową aktywność obok dotychczasowego kontenera dodatkowego. Zadeklaruj konfiguracje zarówno dla podziału A/B, jak i B/C oraz uruchom normalnie działanie C z poziomu B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

Kotlin

class B {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

Reagowanie na zmiany stanu podziału

Różne czynności w aplikacji mogą mieć elementy UI, które pełnią tę samą funkcję, np. element sterujący, który otwiera okno z ustawieniami konta.

Rysunek 23. różne aktywności z funkcjonalnie identycznymi elementami interfejsu;

Jeśli 2 aktywności, które mają wspólny element interfejsu użytkownika, są podzielone, wyświetlanie tego elementu w obu aktywnościach jest zbędne i może wprowadzać użytkowników w błąd.

Rysunek 24. Zduplikowane elementy interfejsu w podziale aktywności.

Aby dowiedzieć się, kiedy działania są w ramach podziału, sprawdź przepływ SplitController.splitInfoList lub zarejestruj listenera z SplitControllerCallbackAdapter, aby otrzymywać powiadomienia o zmianach w stanie 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 {
    lifecycle.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) {
    . . .
    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);
            });
}

Korutyne można uruchamiać w dowolnym stanie cyklu życia, ale zwykle są one uruchamiane w stanie STARTED, aby oszczędzać zasoby (więcej informacji znajdziesz w artykule Korzystanie z koruin Kotlina w komponentach uwzględniających cykl życia).

Callbacki mogą być wywoływane w dowolnym stanie cyklu życia, w tym po zatrzymaniu aktywności. Słuchacze powinni być zwykle zarejestrowani w onStart() i niezarejestrowani w onStop().

Okno modalne na pełnym ekranie

Niektóre czynności blokują użytkownikom interakcję z aplikacją, dopóki nie wykonają określonego działania, np. nie klikną przycisku logowania, nie zaakceptują zasad lub nie usuną komunikatu o błędzie. Działania modalne nie powinny pojawiać się w podgrupie.

Aktywność może być zmuszona do zawsze wypełniania okna zadania za pomocą konfiguracji rozszerzania:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

Zakończ działania

Użytkownicy mogą kończyć aktywności po obu stronach podziału, przesuwając palcem od krawędzi ekranu:

Rysunek 25. Gest przesuwania kończy aktywność B.
Rysunek 26. Gest przesuwania kończy aktywność A.

Jeśli urządzenie jest skonfigurowane tak, aby używać przycisku Wstecz zamiast gestów, dane wejściowe są wysyłane do aktywnej aktywności – aktywności, która została ostatnio dotknięta lub uruchomiona.

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 pary podziałów, aby skonfigurować sposób, w jaki zakończenie wszystkich działań po jednej stronie podziału wpływa na działania po drugiej stronie podziału. Atrybuty:

  • window:finishPrimaryWithSecondary – jak zakończenie wszystkich działań w kontenerze dodatkowym wpływa na działania w kontenerze głównym.
  • window:finishSecondaryWithPrimary – sposób, w jaki zakończenie wszystkich działań w kontenerze głównym wpływa na działania w kontenerze dodatkowym

Możliwe wartości atrybutów:

  • always – zawsze kończ działania w powiązanym kontenerze
  • never – nigdy nie kończ aktywności w powiązanym kontenerze.
  • adjacent – kończyć aktywności w powiązanym kontenerze, gdy dwa kontenery są wyświetlane obok siebie, ale nie wtedy, gdy są ułożone jeden na drugim;

Przykład:

<SplitPairRule
    &lt;!-- Do not finish primary container activities when all secondary container activities finish. --&gt;
    window:finishPrimaryWithSecondary="never"
    &lt;!-- Finish secondary container activities when all primary container activities finish. --&gt;
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Konfiguracja domyślna

Gdy wszystkie aktywności w jednym kontenerze w ramach podziału zostaną zakończone, pozostały kontener zajmie całe okno:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Grupa zawierająca aktywności A i B. A kończy się, a B zajmuje cały ekran.

Grupa zawierająca aktywności A i B. B jest zakończone, a A zajmuje cały ekran.

Dokończenie wspólnych działań

automatycznie kończyć aktywności w kontenerze głównym, gdy wszystkie aktywności w kontenerze dodatkowym zostaną zakończone:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Grupa zawierająca aktywności A i B. Zakończyło się zadanie B, co spowodowało ukończenie zadania A i opróżniło okno zadania.

Grupa zawierająca aktywności A i B. A jest zakończone, a B pozostało w oknie zadania.

automatycznie kończyć działania w kontenerze pomocniczym po zakończeniu wszystkich działań w kontenerze głównym:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Grupa zawierająca aktywności A i B. Zakończono zadanie A, co powoduje ukończenie zadania B, a okno zadania staje się puste.

Grupa zawierająca aktywności A i B. B zakończyło pracę, pozostawiając A w oknie zadania.

Zakończ wszystkie aktywności, gdy zakończą się wszystkie aktywności w kontenerze głównym lub pomocniczym:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Grupa zawierająca aktywności A i B. Zakończono zadanie A, co powoduje ukończenie zadania B, a okno zadania staje się puste.

Grupa zawierająca działania A i B. Zakończono zadanie B, co powoduje również zakończenie zadania A, a okno zadania staje się puste.

Zakończ wiele działań w kontenerach

Jeśli w podzielonym kontenerze ułożone są obok siebie liczne aktywności, zakończenie aktywności na dole nie powoduje automatycznego zakończenia aktywności na górze.

Jeśli np. w kontenerze dodatkowym znajdują się 2 aktywności, a C jest na wierzchu B:

Drugorzędny stos działań zawierający aktywność C, nałożoną na aktywność B, jest nałożony na główny stos działań zawierający aktywność A.

a konfiguracja podziału jest określana przez konfigurację działań A i B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Zakończenie głównej aktywności zachowuje podział.

Podział z działaniem A w kontenerze podstawowym i działaniami B i C w kontenerze dodatkowym, z działaniem C ułożonym na działaniu B. C kończy się, pozostawiając A i B w ramach podziału aktywności.

Zakończenie dolnej (podstawowej) aktywności w kontenerze pomocniczym nie powoduje usunięcia aktywności nad nią, więc podział też nie zostaje usunięty.

Podział z działaniem A w kontenerze podstawowym i działaniami B i C w kontenerze dodatkowym, z działaniem C ułożonym na działaniu B. B kończy się, pozostawiając A i C w ramach podziału aktywności.

Są też wykonywane wszelkie dodatkowe reguły dotyczące kończenia aktywności, np. kończenie aktywności drugorzędnej wraz z główną:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podział z aktywnością A w kontenerze podstawowym i z aktywnościami B i C w kontenerze dodatkowym, przy czym aktywność C jest nałożona na aktywność B. A kończy się, a zarazem kończy się też B i C.

Gdy podział jest skonfigurowany tak, aby podstawowe i dodatkowe grupy docelowe kończyły się razem:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podział z działaniem A w kontenerze podstawowym i działaniami B i C w kontenerze dodatkowym, z działaniem C ułożonym na działaniu B. C kończy się, pozostawiając A i B w podziale aktywności.

Podział z działaniem A w kontenerze podstawowym i działaniami B i C w kontenerze dodatkowym, z działaniem C na wierzchu działania B. B kończy się, pozostawiając A i C w ramach podziału aktywności.

Podział z działaniem A w kontenerze podstawowym i działaniami B i C w kontenerze dodatkowym, z działaniem C ułożonym na działaniu B. A kończy się, a zarazem kończy się też B i C.

Zmiana właściwości podziału w czasie wykonywania

Właściwości aktywnego i widocznego podziału nie można zmienić. Zmiana reguł podziału wpływa na dodatkowe uruchomienia aktywności i nowe kontenery, ale nie dotyczy istniejących i aktywnych podziałów.

Aby zmienić właściwości aktywnych podziałów, zakończ aktywność poboczną lub aktywność poboczne w ramach danego podziału i uruchom je ponownie z nową konfiguracją.

Właściwości podziału dynamicznego

Android 15 (interfejs API 35) i nowsze obsługiwane przez Jetpacka WindowManager 1.4 i nowsze oferują funkcje dynamiczne, które umożliwiają konfigurowanie podziałów w ramach wklejania aktywności, w tym:

  • Rozszerzanie paneli: interaktywny, przeciągany separator umożliwia użytkownikom zmianę rozmiaru paneli w ramach podzielonej prezentacji.
  • Przypinanie elementów w grupie aktywności: użytkownicy mogą przypinać treści w jednym kontenerze i oddzielać nawigację w tym kontenerze od nawigacji w innym kontenerze.
  • Zaciemnianie pełnoekranowego okna dialogowego: podczas wyświetlania okna dialogowego aplikacje mogą określić, czy zaciemnić całe okno zadania, czy tylko kontener, który otworzył okno dialogowe.

Rozwinięcie panelu

Rozszerzenie panelu umożliwia użytkownikom dostosowanie ilości miejsca na ekranie przypisanej do 2 czynności w układzie z 2 panelami.

Aby dostosować wygląd przegrody okna i ustawić zakres przeciągania, wykonaj te czynności:

  1. Utwórz instancję DividerAttributes

  2. Dostosowywanie atrybutów separatora:

    • color: kolor przeciąganego separatora panelu.

    • widthDp: szerokość przeciąganego separatora panelu. Ustaw na WIDTH_SYSTEM_DEFAULT, aby system określił szerokość separatora.

    • Zakres przeciągania:minimalny procent ekranu, który może zajmować dana kolumna. Może przyjmować wartości od 0,33 do 0,66. Ustaw na DRAG_RANGE_SYSTEM_DEFAULT, aby system mógł określić 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(context, 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(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    );
}
SplitAttributes splitAttributes = splitAttributesBuilder.build();

Przypinanie elementów stosu aktywności

Przypinanie grupy aktywności umożliwia użytkownikom przypinanie jednego z okna podzielonego, dzięki czemu aktywność pozostaje bez zmian, gdy użytkownicy poruszają się po drugim oknie. Przypinanie kart zapewnia większą wygodę podczas wielozadaniowości.

Aby umożliwić przypinanie elementów w steku aktywności w aplikacji:

  1. Dodaj do pliku układu przycisk aktywności, którą chcesz przypiąć, np. szczegółową aktywność w układzie 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>
    
  2. W metodzie onCreate() aktywności ustaw odbiornik onclick przycisku:

    Kotlin

    pinButton = 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);
    });

Przyciemnianie okna pełnoekranowego

Aktywności zwykle przyciemniają wyświetlacz, aby zwrócić uwagę na dialog. Aby zapewnić spójność interfejsu użytkownika, w przypadku umieszczania aktywności oba panele wyświetlacza z podwójnym panelem powinny być przyciemnione, a nie tylko panel zawierający aktywność, która otworzyła okno dialogowe.

W ramach WindowManager 1.4 i nowszych po otwarciu okna dialogowego domyślnie przyciemnia się całe okno aplikacji (patrz EmbeddingConfiguration.DimAreaBehavior.ON_TASK).

Aby przyciemnić tylko kontener aktywności, która otworzyła okno, użyj EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.

Przenoszenie aktywności z podzielonego okna do pełnego okna

Utwórz nową konfigurację, która wyświetla pełne okno aktywności pobocznej, a następnie uruchom ponownie aktywność z zamiarem, który przekształca się w tą samą instancję.

Sprawdzanie obsługi podziału w czasie wykonywania

Osadzanie aktywności jest obsługiwane w Androidzie 12L (poziom interfejsu API 32) i nowszych, ale jest też dostępne na niektórych urządzeniach z wersjami platformy starszymi niż ta. Aby sprawdzić dostępność funkcji w czasie wykonywania kodu, 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 rozdzielniki nie są obsługiwane, aktywności są uruchamiane na szczycie stosu aktywności (zgodnie z modelem umieszczania bez aktywności).

Zapobieganie nadpisaniu ustawień systemowych

Producenci urządzeń z Androidem (OEM) mogą implementować funkcję wklejania aktywności jako funkcję systemu urządzenia. System określa reguły podziału dla aplikacji wieloczynnościowych, zastępując zachowanie okien aplikacji. Zastąpienie systemowe powoduje, że aplikacje z wieloma aktywnościami są zmuszane do korzystania z trybu wklejania aktywności zdefiniowanego przez system.

Umieszczanie w ramach systemu może poprawić prezentację aplikacji dzięki układom wielopanelowym, takim jak lista-szczegóły, bez wprowadzania żadnych zmian w aplikacji. Jednak umieszczanie aktywności przez system może też powodować nieprawidłowe układy aplikacji, błędy lub konflikty z implementacją umieszczania aktywności przez aplikację.

Aplikacja może zapobiegać umieszczaniu aktywności systemowej lub zezwalać na to, ustawiając wartość PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE w pliku manifestu aplikacji, na przykład:

<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 usługi jest zdefiniowana w obiekcie Jetpack WindowManager WindowProperties. Ustaw wartość na false, jeśli Twoja aplikacja implementuje osadzenie aktywności, lub jeśli chcesz w inny sposób uniemożliwić systemowi stosowanie do niej reguł osadzania aktywności. Ustaw wartość na true, aby umożliwić systemowi stosowanie do aplikacji osadzania aktywności zdefiniowanej przez system.

Ograniczenia, ograniczenia i zastrzeżenia

  • Tylko aplikacja hosta zadania, 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 zadania obsługujące umieszczanie i podziały są wykonywane w ramach zadania należącego do innej aplikacji, umieszczanie i podziały nie będą działać w przypadku tych zadań.
  • Działania można organizować tylko w ramach jednego zadania. Uruchomienie aktywności w nowym zadaniu zawsze powoduje jej wyświetlenie w nowym oknie poza istniejącymi podziałami.
  • Tylko działania w tym samym procesie można grupować i umieszczać w podziale. W wyniku wywołania zwrotnego SplitInfo są raportowane tylko działania należące do tego samego procesu, ponieważ nie ma możliwości uzyskania informacji o działaniach w różnych procesach.
  • Każda para lub pojedyncza reguła aktywności ma zastosowanie tylko do uruchamiania aktywności, które nastąpiło po zarejestrowaniu reguły. Obecnie nie można aktualizować dotychczasowych podziałów ani ich właściwości wizualnych.
  • Konfiguracja filtra pary rozdzielonej musi odpowiadać intencjom używanym podczas uruchamiania aktywności w pełni. Dopasowanie następuje w momencie, gdy nowa aktywność jest uruchamiana z procesu aplikacji, więc może nie znać nazw komponentów, które są rozwiązywane później w ramach procesu systemowego podczas korzystania z intencji domyślnych. Jeśli nazwa komponentu nie jest znana w momencie uruchomienia, zamiast niej można użyć znaku zastępczego („*/*”), a filtrowanie można wykonać na podstawie działania związanego z zamiarem.
  • Obecnie nie można przenosić działań między kontenerami ani przenosić ich do lub z podgrup po ich utworzeniu. Podziały są tworzone przez bibliotekę WindowManager tylko wtedy, gdy uruchamiane są nowe aktywności z pasującymi regułami. Podziały są usuwane, gdy kończy się ostatnia aktywność w kontenerze podzielonym.
  • Aktywności można ponownie uruchomić, gdy zmieni się konfiguracja. Gdy więc utworzysz lub usuniesz podział i zmienisz granice aktywności, ta może zostać całkowicie zniszczona, a potem utworzona na nowo. Dlatego deweloperzy aplikacji powinni zachować ostrożność w takich kwestiach jak uruchamianie nowych działań z poziomu wywołań cyklu życia.
  • Aby obsługiwać umieszczanie aktywności, urządzenia muszą mieć interfejs rozszerzeń okna. Interfejs ten jest dostępny na prawie wszystkich urządzeniach z dużym ekranem z Androidem 12L (poziom API 32) lub nowszym. Jednak niektóre urządzenia z dużym ekranem, które nie mogą uruchamiać wielu działań, nie mają interfejsu rozszerzeń okien. Jeśli urządzenie z dużym ekranem nie obsługuje trybu wielookienkowego, może nie obsługiwać umieszczania aktywności.

Dodatkowe materiały