Umieszczanie aktywności

Umieszczanie aktywności optymalizuje aplikacje na urządzeniach z dużym ekranem, dzieląc między 2 działaniami lub 2 instancjami tego samego działania.

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

Jeśli Twoja aplikacja składa się z wielu aktywności, umieszczanie aktywności umożliwia zapewniają większą wygodę użytkowników na tabletach, urządzeniach składanych i urządzeniach z ChromeOS.

Wstawianie aktywności nie wymaga refaktoryzacji kodu. Ty decydujesz, jak aplikacja Wyświetla swoje działania – obok siebie lub stos – po utworzeniu pliku XML konfiguracji lub za pomocą wywołań interfejsu API Jetpack WindowManager.

Obsługa małych ekranów jest obsługiwana automatycznie. 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.

Umieszczanie aktywności uwzględnia zmiany orientacji urządzenia i działa bezproblemowo na urządzeniach składanych, układaniu i wyjmowaniu urządzeń podczas składania rozwinie się.

Umieszczanie aktywności jest obsługiwane na większości urządzeń z dużym ekranem z Androidem 12L (poziom interfejsu API 32) i wyższy.

Podziel okno zadania

Umieszczenie aktywności dzieli okno zadania aplikacji na 2 kontenery: podstawowy i dodatkowy. Kontenery zawierają działania rozpoczęte z poziomu głównej aktywności 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 aplikacji.

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:

Rysunek 2. Dwie aktywności obok siebie.

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

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

Działania, które są już podzielone i udostępniają okno zadania, mogą zostać uruchomione inne rodzaje aktywności:

  • Po bokach innej aktywności:

    Rysunek 4. Aktywność A rozpoczyna aktywność C na boku względem aktywności B.
  • Na bok, aby przesunąć podział na bok, ukrywając poprzednią główną aktywność:

    Rysunek 5. Ćwiczenie B rozpoczyna aktywność C na bok i przesuwa dzielą się na bok.
  • Uruchomić aktywność na pierwszym miejscu, czyli na tym samym stosie aktywności:

    Rysunek 6. Aktywność B uruchamia aktywność C bez dodatkowych flag intencji.
  • Uruchom okno pełne aktywności w tym samym zadaniu:

    Rysunek 7. Aktywność A lub B rozpoczyna aktywność C, która wypełnia w oknie zadania.

Nawigacja wstecz

Różne rodzaje aplikacji mogą mieć różne reguły przechodzenia wstecz stanu okna zadań podzielonych w zależności od zależności między działaniami lub sposobu, użytkownicy wywołują zdarzenie wstecz, np.:

  • W powiązaniu: jeśli aktywności są powiązane i jedna z nich nie powinna być wyświetlana bez drugiej, możesz skonfigurować przekierowanie wstecz tak, aby kończyło obie aktywności.
  • 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 używasz nawigacji przyciskami, 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 starszy – zdarzenie wsteczne jest wysyłane do aktywność, podczas której został wykonany gest. Gdy użytkownik przesunie palcem od lewej strony zdarzenie wstecz zostanie wysłane do aktywności po lewej stronie, panelu podziału okna. Gdy użytkownik przesunie palcem od prawej strony zdarzenie wstecz jest wysyłane do aktywności w panelu po prawej stronie.

  • Android 15 (poziom interfejsu API 35) 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 przycisków.

Układ wielopanelowy

Jetpack WindowManager umożliwia tworzenie obiektów osadzonych w wielu panelach układ na urządzeniach z dużym ekranem z Androidem 12L (poziom interfejsu API 32) lub nowszym na niektórych urządzeniach z wcześniejszymi wersjami platformy. Istniejące aplikacje oparte na wielu aktywności, a nie fragmentów czy układów opartych na widokach, SlidingPaneLayout może zapewniać lepsze wrażenia użytkownikom dużych ekranów bez refaktoryzacji kodu źródłowego.

Typowym przykładem jest podział według listy. 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 te 2 czynności są wykonywane jako 1.

Rysunek 8. 2 aktywności rozpoczęte jednocześnie w kilku panelach układ.

Atrybuty podziału

Możesz określić proporcje okna zadania między podzielonymi kontenerami i układu kontenerów względem siebie.

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

  • splitRatio: ustawia 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 lokalnego.

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 Interfejs API WindowManager.

Rysunek 9. Dwa segmenty aktywności rozmieszczone 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ł aktywności. Ostatecznie powinny zostać zastąpione inną aktywnością zawierającą treści. Na przykład w ramach układu listy z szczegółami aktywność zastępcza może zajmować część drugorzędną w ramach podziału aktywności, dopóki nie wybierzesz elementu z listy. Wtedy zastąpi ją aktywność zawierająca szczegółowe informacje o wybranym elemencie listy.

Domyślnie system wyświetla obiekty zastępcze tylko wtedy, gdy jest wystarczająco dużo miejsca na podział aktywności. Symbole zastępcze są automatycznie kończone, gdy rozmiar interfejsu zmiana szerokości lub wysokości jest zbyt mała, aby można było wyświetlić podział. Jeśli pozwala na to ilość miejsca, system ponownie uruchamia element zastępczy w ponownym stanie.

Rysunek 10. Składanie i rozkładanie urządzenia. Obiekt zastępczy a następnie zakończyć działanie i odtworzyć je w przypadku zmiany rozmiaru wyświetlanego obrazu.

Jednak atrybut stickyPlaceholder metody SplitPlaceholderRule lub setSticky() interfejsu SplitPlaceholder.Builder może zastąpić domyślne działanie. Jeśli atrybut lub metoda określa wartość true, system wyświetla placeholder jako główną aktywność w oknie zadania, gdy rozmiar wyświetlacza zostanie zmniejszony z 2 do 1 półki (patrz Konfiguracja podziału).

Rysunek 11. Składanie i rozkładanie składanego urządzenia Plik zastępczyny aktywność jest trwała.

Zmiana rozmiaru okna

Gdy zmiany konfiguracji urządzenia zmniejszają szerokość okna zadania, tak że nie mieści się ono w ramach układu z kilkoma panelami (np. gdy składany duży ekran urządzenia zmienia rozmiar z rozmiaru tabletu na rozmiar telefonu lub gdy zmienia się rozmiar okna aplikacji w trybie wielookiennym), 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 symbol zastępczy jest automatycznie zamykany. Gdy obszar wyświetlania znów stanie się wystarczająco duży, placeholder zostanie utworzony ponownie. (Zobacz Obiekty zastępcze).

Nakładanie 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 kolejności wykonywania działań w tej kolejności:

Podrzędny stos aktywności zawierający aktywność C nałożoną na aktywność B.
          Drugi stos jest ułożony na stosie głównym zawierającym aktywność A.

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

Małe okno, w którym widać tylko aktywność C.

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

Jeśli konfiguracja okna zadań zostanie przywrócona do większego rozmiaru, i mieści się w wielu panelach, aktywności są ponownie wyświetlane obok siebie.

Skumulowane podziały

Ćwiczenie B rozpoczyna aktywność C na bok i przesuwa podział w bok:

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

Efektem jest aktywność w tym samym zadaniu w takiej kolejności:

Aktywności A, B i C w jednym stosie. Ćwiczenia są wyświetlane w stosie
          w następującej 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, w którym widać tylko aktywność C.

Orientacja pionowa

Ustawienie w pliku manifestu android:screenOrientation umożliwia aplikacjom ograniczenie aktywności do orientacji pionowej lub poziomej. Większa wygoda użytkowników na urządzeniach z dużym ekranem, takich jak tablety i urządzenia składane, producenci urządzeń (OEM) mogą ignorować żądania orientacji ekranu i czarne pasy w aplikacji w orientacji pionowej lub poziomą na ekranach pionowych.

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

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

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

Zawsze dodawaj android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED do pliku manifestu aplikacji, aby informować urządzenia, które aplikacja obsługuje umieszczanie aktywności (przeczytaj konfigurację 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 służą do konfigurowania podziałów 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 w Twojej aplikacji jest zaimplementowane umieszczanie aktywności.

    Dodaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED do <aplikacji> pliku manifestu aplikacji i ustaw parametr na „true” (prawda), na przykład:

    <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 usłudze WindowManager w wersji 1.1.0-alfa06 i nowszych podziały wektora dystrybucyjnego aktywności są wyłączone, chyba że właściwość zostanie dodana do pliku manifestu i ma wartość Prawda.

    Producenci urządzeń za pomocą tego ustawienia włączają też niestandardowe funkcje które obsługują umieszczanie aktywności. Na przykład urządzenia mogą używać czarnych pasów do w orientacji pionowej na wyświetlaczach poziomych, przejście do układu z 2 panelami po rozpoczęciu drugiej aktywności (patrz Stała orientacja pionowa).

Konfiguracja XML

Aby utworzyć opartą na XML implementację wektora dystrybucyjnego aktywności, ukończ następujące kroki:

  1. Utwórz plik zasobów XML, który:

    • Definiuje działania o wspólnym podziale
    • Konfiguruje opcje podziału
    • Tworzy obiekt placeholder dla kontenera dodatkowego: podział, 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>
    
  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 aplikacji RuleController podczas uruchamiania aplikacji, dzięki czemu reguły są aktywne, gdy rozpoczyna się jakakolwiek aktywność.

    Aby utworzyć inicjator, wykonaj te czynności:

    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 efekcie, gdy rozpoczyna się główna aktywność aplikacji, reguły podziału są aktywne.

Interfejs WindowManager API

Możesz wdrożyć umieszczanie aktywności automatycznie, korzystając z kilku interfejsów API połączeń. Wykonuj wywołania w metodie onCreate() podklasy Application, aby mieć pewność, że reguły są aktywne przed uruchomieniem aktywności.

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

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

    1. Utwórz SplitPairFilter, który identyfikuje aktywności, które mają ten sam 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 na potrzeby 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 współczynnika określa części dostępnego obszaru wyświetlania przydzielonego kontener główny; kontener dodatkowy zajmuje pozostałą część w dostępnym obszarze wyświetlania.
      • setLayoutDirection(): określa sposób działania kontenerów aktywności są rozmieszczane względem siebie.
    4. 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.Builder tworzy i konfiguruje regułę:

      • filterSet: zawiera filtry pary podziałów, które określają, kiedy stosować regułę, przez identyfikowanie działań, które mają wspólny podział.
      • setDefaultSplitAttributes(): reguła stosuje atrybuty układu.
      • 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: wartością domyślną jest 1,4, czyli powoduje, że czynności zajmują całe okno zadania w orientacji pionowej. na większości tabletów. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT i setMaxAspectRatioInLandscape() Wartość domyślna dla pól orientacja pozioma to ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary(): określa, jak zakończenie wszystkich aktywności w kontenerze drugorzędnym wpływa na aktywności w kontenerze pierwotnym. 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ńczenie działań).
      • setFinishSecondaryWithPrimary(): określa sposób kończenia wszystkich edycji. aktywności w kontenerze głównym wpływają na działania w kontener dodatkowy. ALWAYS oznacza, że system powinien zawsze wykona działania w kontenerze dodatkowym, gdy wszystkie działania w głównym kontenerze (patrz Kończenie działań).
      • setClearTop(): określa, czy wszystkie działania w sekcji kontener dodatkowy jest zakończony po uruchomieniu nowej aktywności w kontener. Wartość false oznacza, że nowe aktywności są dodawane do tych, które są już w kontenerze drugorzędnym.
    5. Pobierz pojedynczą instancję 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. Utwórz obiekt zastępczy kontenera dodatkowego, gdy: treść niedostępna:

    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. Utwórz 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 działania związanego z obiektem zastępczym.
      • setDefaultSplitAttributes(): zastosowanie atrybutów układu do reguły.
      • setMinWidthDp(): Ustawia minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości, dp) umożliwiającą podział.
      • setMinSmallestWidthDp(): Określa minimalną wartość (w dp), jaką ma być mniejsza z tych dwóch wartości muszą umożliwiać podział bez względu na urządzenie. orientacji ekranu.
      • setMaxAspectRatioInPortrait(): Ustawia maksymalny format obrazu (wysokość:szerokość) w orientacji pionowej. orientacja, dla której są wyświetlane 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. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT i setMaxAspectRatioInLandscape() Wartość domyślna w przypadku orientacji poziomej to ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder(): Określa, jak zakończenie działania obiektu zastępczego wpływa na aktywności w kontenerze głównym. ALWAYS oznacza, że system zawsze powinien zakończy działania w kontenerze głównym, gdy obiekt zastępczy (zobacz Kończenie działań).
      • setSticky(): określa, czy aktywność zastępcza ma być widoczna na szczycie grupy aktywności na małych ekranach, gdy zastępcza aktywność zostanie po raz pierwszy wyświetlona w ramach grupy z wystarczającą minimalną szerokością.
    4. Dodaj regułę do interfejsu 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 identyfikujący aktywność, która powinna zawsze zajmują cały obszar wyświetlania zadań:

      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. Tworzenie 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 działań, które chcesz wykluczyć z podziału.
      • setAlwaysExpand(): określa, czy aktywność powinna wypełnić całe okno zadania.
    4. Dodaj regułę do interfejsu WindowManager RuleController:

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

Umieszczenie w innej aplikacji

Na Androidzie 13 (poziom API 33) lub nowszym aplikacje mogą umieszczać w sobie czynności z innych aplikacji. Umieszczanie aktywności w różnych aplikacjach lub przez różne identyfikatory UID umożliwia wizualną integrację działań z wielu aplikacji na Androida. System wyświetla aktywność aplikacji hosta i wbudowaną aktywność z innej aplikacji obok siebie lub u góry i dołu, tak jak w przypadku osadzania aktywności w jednej aplikacji.

Na przykład aplikacja Ustawienia może umieścić w niej aktywność związaną z selektorem tapet aplikacji TapPicker:

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

Model zaufania

Procesy hosta, które zawierają działania z innych aplikacji, mogą ponownie zdefiniować sposób prezentacji umieszczonych elementów, w tym rozmiar, położenie, przycinanie i przejrzystością. Złośliwi hostowie mogą używać tej funkcji, 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 niewłaściwemu używaniu umieszczania aktywności w różnych aplikacjach, Android wymaga włączenia tej zgody przez aplikacje. pozwalają na umieszczanie ich aktywności na innych stronach. Aplikacje mogą oznaczać hosty jako zaufane lub niezaufanych.

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 elementów <activity> lub <application> 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 na temat wygenerowania raportu podpisywania i Uwierzytelnianie klienta.

Niezaufane hosty

Aby umożliwić każdej aplikacji osadzenie 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 umieszczenia niezaufanej aktywności, utwórz niestandardowy uwierzytelniania, który weryfikuje tożsamość hosta. Jeśli znasz gospodarza certyfikatów, użyj biblioteki androidx.security.app.authenticator do uwierzytelnienie. Jeśli host uwierzytelnia się po umieszczeniu Twojej aktywności, możesz wyświetlać 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 w bibliotece Jetpack WindowManager, aby sprawdzić, czy host jest osadzony 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);
}

Minimalne ograniczenie rozmiaru

System Android stosuje minimalną wysokość i szerokość określoną w aplikacji w pliku manifestu <layout> do umieszczonych aktywności. 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, osadzony kontener rozszerzy się, aby wypełnić cały obszar zadania.

<alias-aktywności>

Aby umieszczać zaufane lub niezaufane informacje o aktywności, <activity-alias>, android:knownActivityEmbeddingCerts lub Do docelowej aktywności należy zastosować android:allowUntrustedActivityEmbedding a nie aliasem. Zasada, która sprawdza bezpieczeństwo na serwerze systemowym, jest oparta na flagach ustawionych na obiekcie docelowym, a nie na aliasie.

Aplikacja hosta

Aplikacje hostujące implementują osadzanie aktywności w różnych aplikacjach w taki sam sposób implementować osadzanie aktywności w jednej aplikacji. Obiekty SplitPairRule i SplitPairFilter lub ActivityRule i ActivityFilter określają wbudowane aktywności i podziały okna zadania. Zdefiniowano reguły podziału statycznie w formacie XML lub w czasie działania za pomocą Jetpacka. Wywołania interfejsu WindowManager API.

Jeśli aplikacja hosta próbuje osadzić działanie, które nie ma zgody na w przypadku umieszczania treści w różnych aplikacjach, aktywność zajmuje cały granicę zadania. W rezultacie aplikacje hosta muszą wiedzieć, czy działania docelowe umożliwiają korzystanie z różnych aplikacji wektora dystrybucyjnego.

Jeśli osadzona aktywność rozpoczyna nową aktywność w ramach tego samego zadania, a nowe aktywność nie zezwala na umieszczanie w innych aplikacjach, aktywność zajmuje całe granice zadania, zamiast nakładać aktywność w umieszczonym kontenerze.

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

Przykłady podziału

Podziel od całego okna

Rysunek 15. Aktywność A rozpoczyna aktywność B z boku.

Refaktoryzacja nie jest wymagana. Możesz zdefiniować konfigurację dla podziału statyczne lub w czasie działania, a następnie wywoływać metodę Context#startActivity() bez żadnych dodatkowych parametrów.

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

Domyślne dzielenie

Gdy strona docelowa aplikacji jest podzielona na 2 kontenery na dużych ekranach, użytkownik ma najlepsze wrażenia, gdy obie czynności są tworzone i prezentowane jednocześnie. Treści mogą jednak nie być dostępne dla drugiego kontenera w ramach podziału, dopóki użytkownik nie wejdzie w interakcję z działalnością w kontenerze głównym (np. nie wybierze elementu z menu nawigacyjnego). Działanie tymczasowe może wypełniać lukę do momentu, aż pojawi się treść można wyświetlić w dodatkowym kontenerze podziału (patrz Obiekty zastępcze).

Rysunek 16. Podział utworzony przez otwarcie 2 zadań jednocześnie. Jedno działanie jest zarezerwowane.

Aby to zrobić, utwórz zmienną i powiąż ją z obiektem zastępczym główne działanie:

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

Gdy aplikacja otrzyma intencję, może wyświetlić aktywność docelową 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 wyświetlaczach szczegóły jest widoczna w pełnym oknie zadania; na większych urządzeniach.

Rysunek 17. szczegółowe informacje o precyzyjnych linkach wyświetlane samodzielnie na małym ekranie; ale razem z działaniami na dużym ekranie.

Prośba o uruchomienie powinna być przekierowana do głównego działania, a szczegóły miejsca docelowego aktywność powinna być uruchamiana z podziałem. System automatycznie wybiera odpowiednią prezentację – obok siebie lub w wielokrotnym wierszu – na podstawie dostępnej szerokości wyświetlania.

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 na ostatnim poziomie nawigacji wstecz. Możesz też uniknąć zamykania aktywności z informacjami i zostawić tylko główną aktywność:

Duża tabela z aktywnościami na liście i szczegółami obok siebie.
          Nawigacja wstecz nie może odrzucić aktywności szczegółów i opuścić listy
          aktywność na ekranie.

Mała wyświetlana reklama z tylko szczegółami aktywności. Nie można przejść wstecz
          odrzuć aktywność związaną z szczegółami i odkryj aktywność na liście.

Zamiast tego możesz zakończyć oba działania 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 kontenerach podzielonych

Gromadzenie wielu aktywności w podzielonym kontenerze umożliwia użytkownikom dostęp 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));
    }
}

Działanie podrzędne jest umieszczone nad działaniem szczegółów i jest ukrywane:

Użytkownik może wtedy wrócić do poprzedniego poziomu szczegółów, wracając do poprzedniego poziomu w ramach stosu:

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

Nakładanie się działań na siebie jest domyślne. są uruchamiane na podstawie aktywności w tym samym kontenerze dodatkowym. Aktywności uruchomione z kontenera głównego w ramach aktywnego podziału również trafiają do na drugim kontenerze aktywności.

Aktywności w nowym zadaniu

Gdy czynności w oknie podzielonego zadania uruchamiają czynności w nowym zadaniu, nowe zadanie jest oddzielone od zadania, które zawiera podział, i wyświetlane w pełnym oknie. Ekran Ostatnie przedstawia dwa zadania: zadanie w podziale i nowe zadanie.

Rysunek 20. Rozpocznij zadanie C w nowym zadaniu na podstawie aktywności B.

Zastępowanie 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 na przykład masz menu w oraz ekrany A i B na panelu dodatkowym, gdy użytkownik składa telefon, B znajduje się nad A, a A na górze menu. Gdy użytkownik przechodzi z pozycji B, zamiast menu pojawia się A.

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

Domyślne zachowanie przy uruchamianiu z boku w nowym kontenerze przez istniejący podział to umieszczenie nowych kontenerów dodatkowych na początku i zachowanie starych. te z tyłu. 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 w kontenerze pomocniczym, a z głównej aktywności (menu) wysyłać nowe intencje, które odwołują się do tej samej instancji, ale wywołują stan lub aktualizację interfejsu w kontenerze pomocniczym.

Wiele podziałów

Aplikacje mogą zapewniać głęboką nawigację wielopoziomową dzięki uruchamianiu dodatkowych działań z boku.

Gdy działanie w kontenerze dodatkowym uruchamia nową aktywność z boku, nowy podział zostanie utworzony w miejsce istniejącego podziału.

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

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

Działania A, B i C w grupie. Ćwiczenia są łączone w grupy
          w następującej kolejności od góry do dołu: C, B, A.

Aby utworzyć nowy podział, uruchom nową aktywność z boku istniejącego kontener dodatkowy. Zadeklaruj konfiguracje dla podziału A/B i B/C i uruchom działanie C, zwykle od 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 działania w aplikacji mogą mieć elementy interfejsu o identycznym działaniu funkcji; na przykład element sterujący, który otwiera okno z kontem ustawieniach.

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ą w ramach podziału, 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 śledzić zmiany w stanie podziału. Następnie: odpowiednio 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).

Wywołania zwrotne mogą być wykonywane w dowolnym stanie cyklu życia, w tym wtedy, gdy działanie jest zatrzymano. 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.

Działanie może wymusić zawsze wypełnienie okna zadania za pomocą polecenia rozwijania Konfiguracja:

<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. Kończenie ćwiczenia za pomocą gestu przesuwania B.
Rysunek 26. Gest przesuwania kończy aktywność A.

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

Wpływ ukończenia wszystkich aktywności w kontenerze na przeciwny kontener zależy od konfiguracji podziału.

Atrybuty konfiguracji

Możesz określić atrybuty reguły podziału pary, aby skonfigurować sposób wykończenia wszystkich aktywności po jednej stronie podziału wpływa na działania po drugiej stronie podział. Atrybuty:

  • window:finishPrimaryWithSecondary – sposób, w jaki 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 dokończ działań 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;

Na 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>

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

Grupa zawierająca aktywności A i B. skończono B, a A do
          zajmuje całe okno.

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>

Podział zawierający aktywności A i B. skończyliśmy B, a także
          kończy działanie A, pozostawiając okno zadania puste.

Podział zawierający aktywności A i B. A jest ukończone, a B pozostaje 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>

Podział zawierający aktywności A i B. Zakończono zadanie A, co powoduje również ukończenie zadania B, a okno zadania staje się puste.

Podział zawierający aktywności A i B. skończono B, zostawiasz A bez zmian.
          w oknie zadania.

Zakończ wszystkie zadania, gdy zakończą się wszystkie zadania 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 również ukończenie zadania B, a okno zadania staje się puste.

Podział zawierający aktywności 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 u góry.

Jeśli na przykład w kontenerze dodatkowym znajdują się 2 działania, nadrzędne są działania C nad nimi:

Dodatkowy stos aktywności zawierający aktywność C nakładany na grupę B
          jest nakładany na podstawowy stos aktywności zawierający aktywność
          Odpowiedź:

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

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

do zakończenia głównej aktywności zachowuje podział.

Podział z aktywizmem A w kontenerze głównym i z aktywizmami B i C w kontenerze dodatkowym, przy czym aktywność C jest nałożona na aktywność B. C kończy, pozostawiając A i B w
          podział aktywności.

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

Podział z aktywizmem A w kontenerze głównym i z aktywizmami B i C w kontenerze dodatkowym, przy czym aktywność C jest nałożona na aktywność B. B kończy się, pozostawiając A i C w ramach podziału aktywności.

Wszelkie dodatkowe reguły dotyczące wspólnego kończenia działań, np. ukończenie Aktywność dodatkowa z podstawową jest też wykonywana:

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

Podział z aktywnością A w kontenerze głównym i z aktywnościami B i C w kontenerze dodatkowym, przy czym aktywność C jest nałożona na aktywność B. Meta
          na końcu B i C.

Gdy podział zostanie skonfigurowany tak, aby zakończyć razem: główną i dodatkową:

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

Podział z aktywizmem A w kontenerze głównym i z aktywizmami B i C w kontenerze dodatkowym, przy czym aktywność C jest nałożona na aktywność B. C kończy się, pozostawiając A i B w ramach podziału aktywności.

Podział z aktywizmem A w kontenerze głównym i z aktywizmami B i C w kontenerze dodatkowym, przy czym aktywność C jest nałożona na aktywność B. B kończy, a A i C pozostają w
          podział aktywności.

Podział z aktywizmem A w kontenerze głównym i z aktywizmami B i C w kontenerze dodatkowym, przy czym aktywność C jest nałożona na aktywność B. A na finiszach, na końcu B
          C:

Zmiana właściwości podziału w czasie działania

Właściwości aktywnego i widocznego podziału nie można zmienić. Zmiana wartości reguły podziału mają wpływ na dodatkowe uruchamianie aktywności i nowe kontenery, ale nie istniejące i aktywne podziały.

Aby zmienić właściwości aktywnych podziałów, zakończ aktywność dodatkową lub i uruchomić ponownie z nową konfiguracją.

Właściwości podziału dynamicznego

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

  • Rozszerzanie paneli: interaktywny, przeciągany separator umożliwia użytkownikom zmianę rozmiaru paneli w ramach podzielonej prezentacji.
  • Przypinanie aktywności: użytkownicy mogą przypinać treści w jednym kontenerze, oddzielić nawigację w kontenerze od nawigacji w drugim kontenerze.
  • Pełnoekranowe okno dialogowe: podczas wyświetlania okna aplikacje mogą określać, czy przyciemnić całe okno zadania, czy tylko kontener, który go otworzył .

Rozwinięcie panelu

Rozwijanie panelu umożliwia użytkownikom dostosowanie ilości miejsca na ekranie przydzielonego w układzie 2 paneli.

Aby dostosować wygląd separatora okien oraz ustawić zakres, który można przeciągnąć, wykonaj następujące czynności:

  1. Utwórz instancję DividerAttributes

  2. Dostosuj atrybuty separatora:

    • color: kolor separatora okien, który można przeciągać.

    • widthDp: szerokość separatora okien, który można przeciągać. Ustaw na WIDTH_SYSTEM_DEFAULT, aby system określił rozgraniczenie .

    • Zakres przeciągania: minimalny odsetek ekranu, który może zajmować dana kolumna. Może się mieścić w zakresie od 0,33 do 0,66. Ustaw na DRAG_RANGE_SYSTEM_DEFAULT, aby system określił przeciąganie zakres dat.

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 aktywności

Przypinanie aktywności umożliwia użytkownikom przypięcie jednego z podzielonych okien, dzięki czemu aktywność pozostaje bez zmian, gdy użytkownicy przechodzą do innego okna. Przypięcie aktywności zapewnia większą wygodę podczas wielozadaniowości.

Aby włączyć przypinanie aktywności w aplikacji, wykonaj te czynności:

  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 detektor funkcji „onclick” na przycisk:

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

przyciemnienie pełnego ekranu,

Aktywności zwykle przyciemniają wyświetlacz, aby zwrócić uwagę na dialog. W wychwytywania aktywności, oba panele wyświetlacza z podwójnym panelem powinny przyciemnić się, a nie przyciemnić. panel zawierający aktywność, która otworzyła okno, w przypadku ujednoliconego interfejsu z myślą o użytkownikach.

W ramach WindowManager 1.4 i nowszych po otwarciu okna dialogowego całe okno aplikacji jest domyślnie przyciemniane (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 tę aktywność z zamiarem, który przekierowuje do tej samej instancji.

Sprawdzanie obsługi podziału w czasie działania

Umieszczanie aktywności jest obsługiwane na Androidzie 12L (poziom interfejsu API 32) i nowszych, ale są też dostępne na niektórych urządzeniach z wcześniejszymi wersjami platformy. 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).

Zapobiegaj zastąpieniu systemu

producenci urządzeń z Androidem (producentom oryginalnego sprzętu; OEM) mogą wdrożyć umieszczanie aktywności jako funkcję systemu urządzeń. System określa reguły podziału dla aplikacji wielozadaniowych, zastępując zachowanie okna aplikacji. Zastąpienie systemowe powoduje, że aplikacje z wieloma działaniami są zmuszane do korzystania z trybu wklejania definiowanego przez system.

Umieszczanie aktywności 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 systemu może też powodować nieprawidłowe układy aplikacji, błędy lub konflikty z implementacją umieszczania aktywności przez aplikację.

Aplikacja może zablokować umieszczanie aktywności systemu lub zezwolić na to, ustawiając parametr property 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 właściwości jest określona w menedżerze okien Jetpack WindowManager WindowProperties obiektu. Ustaw wartość false, jeśli w Twojej aplikacji jest zaimplementowane umieszczanie aktywności. jeśli chcesz w inny sposób uniemożliwić systemowi stosowanie osadzania aktywności reguły aplikacji. Ustaw wartość na true, aby system mógł go zastosować. zdefiniowane przez system aktywność w aplikacji.

Ograniczenia, ograniczenia i zastrzeżenia

  • Tylko hostująca aplikacja zadania, która jest identyfikowana jako właściciel katalogu głównego może porządkować i umieszczać w zadaniu inne działania. Jeśli działania obsługujące umieszczanie i podziały są uruchamiane w zadaniu należącym do do innej aplikacji, osadzanie i podziały 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 jej wyświetlenie w nowym oknie bez względu na istniejące podziały.
  • Do podziału można stosować tylko działania w tym samym procesie. SplitInfo wywołanie zwrotne zgłasza tylko te aktywności, które należą do tej samej aktywności ponieważ nie ma możliwości uzyskania informacji o działaniach w różnych
  • 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 działań 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 w intencji.
  • Obecnie nie można przenosić działań między kontenerami ani w ramach podziałów 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.
  • Działania można uruchamiać ponownie po zmianie konfiguracji, np. w przypadku zostanie utworzony lub usunięty, a granice aktywności zmienią się, aktywność może przez całkowite zniszczenie poprzedniej instancji i utworzenie nowy. Dlatego deweloperzy aplikacji powinni uważać na: podczas uruchamiania nowych działań z wywołań zwrotnych 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. 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