Incorporamento delle attività

L'inserimento di attività ottimizza le app sui dispositivi con schermi di grandi dimensioni dividendo la finestra delle attività di un'applicazione tra due attività o due istanze della stessa attività.

Figura 1. App Impostazioni con attività affiancate.

Se la tua app è composta da più attività, l'inserimento delle attività ti consente di offrire un'esperienza utente migliorata su tablet, dispositivi pieghevoli e ChromeOS.

L'inserimento di attività non richiede il refactoring del codice. Puoi determinare in che modo la tua app visualizza le attività, affiancate o impilate, creando un file di configurazione XML o effettuando chiamate all'API Jetpack WindowManager.

Il supporto per gli schermi di piccole dimensioni viene mantenuto automaticamente. Quando l'app è su un dispositivo con uno schermo piccolo, le attività vengono impilate una sopra l'altra. Sugli schermi di grandi dimensioni, le attività vengono visualizzate una accanto all'altra. Il sistema determina la presentazione in base alla configurazione che hai creato, senza bisogno di logica di ramificazione.

L'inserimento di attività è compatibile con le modifiche dell'orientamento del dispositivo e funziona perfettamente su dispositivi pieghevoli, impilando e scompilando le attività man mano che il dispositivo si apre e si chiude.

L'inserimento di attività è supportato sulla maggior parte dei dispositivi con schermo di grandi dimensioni con Android 12L (livello API 32) e versioni successive.

Finestra dell'attività divisa

L'inserimento di attività suddivide la finestra delle attività dell'app in due contenitori: principale e secondario. I contenitori contengono le attività lanciate dall'attività principale o da altre attività già presenti nei contenitori.

Le attività vengono impilate nel contenitore secondario man mano che vengono lanciate e il contenitore secondario viene sovrapposto al contenitore principale su schermi di piccole dimensioni, pertanto l'impilamento delle attività e la navigazione a ritroso sono in linea con l'ordine delle attività già integrate nella tua app.

L'inserimento delle attività ti consente di visualizzarle in diversi modi. La tua app può suddividere la finestra delle attività lanciando contemporaneamente due attività affiancate:

Figura 2. Due attività affiancate.

In alternativa, un'attività che occupa l'intera finestra delle attività può creare una suddivisione avviando una nuova attività accanto:

Figura 3. L'attività A avvia l'attività B a lato.

Le attività già in una visualizzazione divisa e che condividono una finestra delle attività possono avviare altre attività nei seguenti modi:

  • A lato sopra un'altra attività:

    Figura 4. L'attività A avvia l'attività C a lato dell'attività B.
  • A lato e sposta la suddivisione lateralmente, nascondendo l'attività principale precedente:

    Figura 5. L'attività B avvia l'attività C di lato e sposta la suddivisione di lato.
  • Avvia un'attività in primo piano, ovvero nello stesso stack di attività:

    Figura 6. L'attività B avvia l'attività C senza flag di intent aggiuntivi.
  • Avvia una finestra completa dell'attività nella stessa attività:

    Figura 7. L'attività A o B avvia l'attività C che riempie la finestra delle attività.

Navigazione a ritroso

Tipi diversi di applicazioni possono avere regole di navigazione a ritroso diverse in uno stato della finestra dell'attività divisa a seconda delle dipendenze tra le attività o del modo in cui gli utenti attivano l'evento Indietro, ad esempio:

  • In coppia: se le attività sono correlate e una non deve essere mostrata senza l'altra, la navigazione a ritroso può essere configurata per completare entrambe.
  • Attività indipendenti: se le attività sono completamente indipendenti, il ritorno all'attività precedente non influisce sullo stato di un'altra attività nella finestra delle attività.

L'evento indietro viene inviato all'ultima attività attivata quando si utilizza la navigazione con i pulsanti.

Per la navigazione basata sui gesti:

  • Android 14 (livello API 34) e versioni precedenti: l'evento Indietro viene inviato all'attività in cui si è verificato il gesto. Quando gli utenti scorrono dal lato sinistro schiermo, l'evento Indietro viene inviato all'attività nel riquadro sinistra della finestra divisa. Quando gli utenti scorrono dal lato destro dello schermo, l'evento Indietro viene inviato all'attività nel riquadro a destra.

  • Android 15 (livello API 35) e versioni successive

    • Quando si gestiscono più attività della stessa app, il gesto completa l'attività in primo piano indipendentemente dalla direzione dello scorrimento, offrendo un'esperienza più unificata.

    • Negli scenari che coinvolgono due attività di app diverse (sovrapposizione), l'evento indietro viene indirizzato all'ultima attività attiva, in linea con il comportamento della navigazione con i pulsanti.

Layout a più riquadri

Jetpack WindowManager ti consente di creare un'attività che incorpora un layout con più riquadri su dispositivi con schermi grandi con Android 12L (livello API 32) o versioni successive e su alcuni dispositivi con versioni precedenti della piattaforma. Le app esistenti basate su più attività anziché su frammenti o layout basati su visualizzazioni, come SlidingPaneLayout, possono offrire un'esperienza utente migliorata su schermi di grandi dimensioni senza eseguire il refactoring del codice sorgente.

Un esempio comune è la suddivisione in elenco e dettagli. Per garantire una presentazione di alta qualità, il sistema avvia l'attività di elenco, dopodiché l'applicazione avvia immediatamente l'attività dei dettagli. Il sistema di transizione attende fino a quando entrambe le attività non vengono disegnate, quindi le mostra insieme. Per l'utente, le due attività vengono avviate come una sola.

Figura 8. Due attività sono state avviate contemporaneamente in un layout con più riquadri.

Attributi suddivisi

Puoi specificare la proporzione della finestra delle attività tra i contenitori suddivisi e la disposizione dei contenitori rispetto l'uno all'altro.

Per le regole definite in un file di configurazione XML, imposta i seguenti attributi:

  • splitRatio: imposta le proporzioni del contenitore. Il valore è un numero in virgola mobile nell'intervallo aperto (0,0, 1,0).
  • splitLayoutDirection: specifica la disposizione dei container suddivisi rispetto a un altro. I valori includono:
    • ltr: da sinistra a destra
    • rtl: da destra a sinistra
    • locale: ltr o rtl viene determinato in base all'impostazione internazionale

Per esempi, consulta la sezione Configurazione XML.

Per le regole create utilizzando le API WindowManager, crea un oggetto SplitAttributes con SplitAttributes.Builder e chiama i seguenti metodi di compilatore:

Per esempi, consulta la sezione API WindowManager.

Figura 9. Due suddivisioni delle attività disposte da sinistra a destra, ma con rapporti di suddivisione diversi.

Segnaposto

Le attività segnaposto sono attività secondarie vuote che occupano un'area di una suddivisione delle attività. Alla fine, devono essere sostituite da un'altra attività che contenga contenuti. Ad esempio, un'attività segnaposto potrebbe occupare il lato secondario di un'attività divisa in un layout di elenco dettagliato finché non viene selezionato un elemento dell'elenco, a quel punto un'attività contenente le informazioni dettagliate per l'elemento dell'elenco selezionato sostituisce il segnaposto.

Per impostazione predefinita, il sistema mostra i segnaposto solo quando è presente spazio sufficiente per una suddivisione delle attività. I segnaposto vengono completati automaticamente quando le dimensioni del display diventano una larghezza o un'altezza troppo piccole per visualizzare una suddivisione. Quando lo spazio lo consente, il sistema riavvia il segnaposto con uno stato reinizializzato.

Figura 10. Dispositivo pieghevole che si piega e si apre. L'attività del segnaposto viene completata e ricreata quando le dimensioni del display cambiano.

Tuttavia, l'attributo stickyPlaceholder di un metodo SplitPlaceholderRule o setSticky() di SplitPlaceholder.Builder può sostituire il comportamento predefinito. Quando l'attributo o il metodo specifica un valore true, il sistema mostra il segnaposto come attività principale nella finestra delle attività quando la visualizzazione viene ridotta a un riquadro da una visualizzazione a due riquadri (per un esempio, consulta Configurazione divisa).

Figura 11. Dispositivo pieghevole che si piega e si apre. L'attività del segnaposto è fissa.

Modifiche alle dimensioni della finestra

Quando le modifiche alla configurazione del dispositivo riducono la larghezza della finestra delle attività in modo che non sia sufficientemente grande per un layout a più riquadri (ad esempio, quando un dispositivo pieghevole con schermo grande si piega dalle dimensioni di un tablet a quelle di uno smartphone o la finestra dell'app viene ridimensionata in modalità multi-finestra), le attività non segnaposto nel riquadro secondario della finestra delle attività vengono impilate sopra le attività nel riquadro principale.

Le attività segnaposto vengono mostrate solo quando la larghezza di visualizzazione è sufficiente per una suddivisione. Su schermi più piccoli, il segnaposto viene ignorato automaticamente. Quando l'area di visualizzazione diventa di nuovo sufficientemente grande, il segnaposto viene ricreato. (vedi la sezione Segnaposto).

La sovrapposizione delle attività è possibile perché WindowManager ordina le attività nel riquadro secondario sopra le attività nel riquadro principale.

Più attività nel riquadro secondario

L'attività B avvia l'attività C in situ senza flag di intent aggiuntivi:

Suddivisione delle attività contenente le attività A, B e C con C sovrapposta a B.

Il risultato è il seguente ordine Z delle attività nella stessa attività:

Serie di attività secondarie contenente l'attività C sovrapposta a B.
          Lo stack secondario è sovrapposto allo stack delle attività principali
          contenente l'attività A.

Di conseguenza, in una finestra delle attività più piccola, l'applicazione si riduce a una singola attività con C nella parte superiore dello stack:

Piccola finestra che mostra solo l'attività C.

Se torni indietro nella finestra più piccola, puoi spostarti tra le attività impilate una sopra l'altra.

Se la configurazione della finestra dell'attività viene ripristinata a dimensioni maggiori che possono ospitare più riquadri, le attività vengono visualizzate di nuovo una accanto all'altra.

Suddivisioni in pila

L'attività B avvia l'attività C di lato e sposta la suddivisione lateralmente:

Finestra delle attività che mostra le attività A e B, quindi le attività B e C.

Il risultato è il seguente ordine Z delle attività nella stessa attività:

Attività A, B e C in un unico elenco. Le attività sono impilate
          nel seguente ordine, dall'alto verso il basso: C, B, A.

In una finestra delle attività più piccola, l'applicazione si riduce a una singola attività con C in cima:

Piccola finestra che mostra solo l'attività C.

Orientamento verticale fisso

L'impostazione manifest android:screenOrientation consente alle app di limitare le attività all'orientamento verticale o orizzontale. Per migliorare l'esperienza utente su dispositivi con schermi di grandi dimensioni, come tablet e dispositivi pieghevoli, i produttori di dispositivi (OEM) possono ignorare le richieste di orientamento dello schermo e applicare la modalità letterbox all'app in orientamento verticale su display orizzontali o in orientamento orizzontale su display verticali.

Figura 12. Attività con letterbox: formato verticale fisso su dispositivo orizzontale (a sinistra), formato orizzontale fisso su dispositivo verticale (a destra).

Analogamente, quando l'inserimento di attività è attivo, gli OEM possono personalizzare i dispositivi per visualizzare le attività in formato letterbox con orientamento verticale fisso in orizzontale su schermi di grandi dimensioni (larghezza ≥ 600 dp). Quando un'attività in modalità Ritratto fissa avvia una seconda attività, il dispositivo può visualizzare le due attività una accanto all'altra in una visualizzazione a due riquadri.

Figura 13. L'attività A in formato Ritratto fisso avvia l'attività B a lato.

Aggiungi sempre la proprietà android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED al file manifest dell'app per informare i dispositivi che la tua app supporta l'embedding delle attività (vedi la sezione Configurazione della suddivisione). I dispositivi personalizzati dall'OEM possono quindi determinare se applicare il letterbox alle attività in formato Ritratto fisso.

Configurazione della suddivisione

Le regole di suddivisione configurano le suddivisioni delle attività. Puoi definire le regole di suddivisione in un file di configurazione XML o tramite chiamate all'API WindowManager di Jetpack.

In entrambi i casi, l'app deve accedere alla libreria WindowManager e deve informare il sistema che ha implementato l'inserimento di attività.

Procedi nel seguente modo:

  1. Aggiungi la dipendenza della libreria WindowManager più recente al file build.gradle a livello di modulo dell'app, ad esempio:

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

    La libreria WindowManager fornisce tutti i componenti necessari per l'embedding delle attività.

  2. Comunica al sistema che la tua app ha implementato l'inserimento di attività.

    Aggiungi la proprietà android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED all'elemento <application> del file manifest dell'app e imposta il valore su true, ad esempio:

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

    Nella versione 1.1.0-alpha06 e successive di WindowManager, le suddivisioni dell'inserimento di attività sono disattivate, a meno che la proprietà non venga aggiunta al manifest e impostata su true.

    Inoltre, i produttori di dispositivi utilizzano l'impostazione per attivare funzionalità personalizzate per le app che supportano l'inserimento di attività. Ad esempio, i dispositivi possono applicare il letterbox a un'attività solo in formato verticale su schermi orizzontali per orientarla in vista della transizione a un layout a due riquadri all'avvio di una seconda attività (vedi Orientamento verticale fisso).

Configurazione XML

Per creare un'implementazione basata su XML dell'inserimento di attività, completa i seguenti passaggi:

  1. Crea un file di risorse XML che:

    • Definisce le attività che condividono una suddivisione
    • Configura le opzioni di suddivisione
    • Crea un segnaposto per il contenitore secondario della suddivisione quando i contenuti non sono disponibili
    • Specifica le attività che non devono mai far parte di una suddivisione

    Ad esempio:

    <!-- 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. Crea un'inizializzazione.

    Il componente WindowManager RuleController analizza il file di configurazione XML e rende disponibili le regole per il sistema. Una libreria Startup di Jetpack Initializer rende disponibile il file XML per RuleController all'avvio dell'app in modo che le regole siano in vigore all'avvio di qualsiasi attività.

    Per creare un'inizializzazione:

    1. Aggiungi la dipendenza della libreria Jetpack Startup più recente al file build.gradle a livello di modulo, ad esempio:

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

    2. Crea una classe che implementi l'interfaccia Initializer.

      L'inizializzatore rende disponibili le regole di suddivisione per RuleController passando l'ID del file di configurazione XML (main_split_config.xml) al metodo 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. Crea un fornitore di contenuti per le definizioni delle regole.

    Aggiungi androidx.startup.InitializationProvider al file manifest dell'app come <provider>. Includi un riferimento all'implementazione dell'inizializzatore 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 rileva e inizializza SplitInitializer prima della chiamata al metodo onCreate() dell'app. Di conseguenza, le regole di suddivisione vengono applicate all'avvio dell'attività principale dell'app.

API WindowManager

Puoi implementare l'inserimento di attività in modo programmatico con alcune chiamate API. Esegui le chiamate nel metodo onCreate() di una sottoclasse di Application per assicurarti che le regole siano in vigore prima del lancio di qualsiasi attività.

Per creare un'attività suddivisa in modo programmatico:

  1. Crea una regola di suddivisione:

    1. Crea un SplitPairFilter che identifichi le attività che condividono la suddivisione:

      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. Aggiungi il filtro a un insieme di filtri:

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
    3. Crea gli attributi del layout per la suddivisione:

      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 crea un oggetto contenente gli attributi di layout:

      • setSplitType(): definisce in che modo l'area di visualizzazione disponibile viene assegnata a ciascun contenitore di attività. Il tipo di suddivisione in base al rapporto specifica la proporzione dell'area di visualizzazione disponibile allocata al contenitore principale. Il contenitore secondario occupa la parte rimanente dell'area di visualizzazione disponibile.
      • setLayoutDirection(): specifica la disposizione dei contenitori delle attività rispetto l'uno all'altro, prima il contenitore principale.
    4. Crea un 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 crea e configura la regola:

      • filterSet: contiene filtri di coppie di suddivisioni che determinano quando applicare la regola identificando le attività che condividono una suddivisione.
      • setDefaultSplitAttributes(): applica gli attributi di layout alla regola.
      • setMinWidthDp(): imposta la larghezza minima del display (in pixel indipendenti dalla densità, dp) che consente una suddivisione.
      • setMinSmallestWidthDp(): imposta il valore minimo (in dp) che deve avere la dimensione di visualizzazione più piccola per attivare una suddivisione indipendentemente dall'orientamento del dispositivo.
      • setMaxAspectRatioInPortrait(): imposta il formato massimo della visualizzazione (altezza:larghezza) in orientamento verticale per cui vengono visualizzate le suddivisioni delle attività. Se le proporzioni di un display in formato verticale superano le proporzioni massime, le suddivisioni vengono disattivate indipendentemente dalla larghezza del display. Nota:il valore predefinito è 1,4, il che fa sì che le attività occupino l'intera finestra delle attività in orientamento verticale sulla maggior parte dei tablet. Vedi anche SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT e setMaxAspectRatioInLandscape(). Il valore predefinito per landscape è ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary(): imposta il modo in cui il completamento di tutte le attività nel contenitore secondario influisce sulle attività nel contenitore principale. NEVER indica che il sistema non deve completare le attività principali al termine di tutte le attività nel contenitore secondario (vedi Completare le attività).
      • setFinishSecondaryWithPrimary(): imposta il modo in cui il completamento di tutte le attività nel contenitore principale influisce sulle attività nel contenitore secondario. ALWAYS indica che il sistema deve sempre completare le attività nel contenitore secondario quando tutte le attività nel contenitore principale sono state completate (vedi Completare le attività).
      • setClearTop(): specifica se tutte le attività nel contenuto secondario sono terminate quando viene avviata una nuova attività nel contenuto. Un valore false specifica che le nuove attività vengono impilate sulle attività già presenti nel contenitore secondario.
    5. Recupera l'istanza singleton di WindowManager RuleController e aggiungi la regola:

      Kotlin

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

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. Crea un segnaposto per il contenitore secondario quando i contenuti non sono disponibili:

    1. Crea un ActivityFilter che identifichi l'attività con cui il segnaposto condivide una suddivisione della finestra delle attività:

      Kotlin

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

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. Aggiungi il filtro a un insieme di filtri:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
    3. Crea un 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 crea e configura la regola:

      • placeholderActivityFilterSet: contiene filtri attività che determinano quando applicare la regola identificando le attività a cui è associata l'attività segnaposto.
      • Intent: specifica il lancio dell'attività segnaposto.
      • setDefaultSplitAttributes(): applica gli attributi di layout alla regola.
      • setMinWidthDp(): imposta la larghezza minima del display (in pixel indipendenti dalla densità, dp) che consente una suddivisione.
      • setMinSmallestWidthDp(): imposta il valore minimo (in dp) che deve avere la più piccola delle due dimensioni dello schermo per consentire una suddivisione indipendentemente dall'orientamento del dispositivo.
      • setMaxAspectRatioInPortrait(): imposta il formato massimo dello schermo (altezza:larghezza) in formato verticale per cui vengono visualizzate le suddivisioni delle attività. Nota:il valore predefinito è 1,4, il che fa sì che le attività riempiano la finestra delle attività in formato verticale sulla maggior parte dei tablet. Vedi anche SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT e setMaxAspectRatioInLandscape(). Il valore predefinito per il formato orizzontale è ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder(): imposta in che modo il completamento dell'attività segnaposto influisce sulle attività nel contenitore principale. SEMPRE indica che il sistema deve sempre completare le attività nel contenitore principale al termine del segnaposto (vedi Completa attività).
      • setSticky(): determina se l'attività segnaposto viene visualizzata sopra la serie di attività su display di piccole dimensioni dopo la prima visualizzazione del segnaposto in una suddivisione con larghezza minima sufficiente.
    4. Aggiungi la regola a WindowManager RuleController:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. Specifica le attività che non devono mai far parte di una suddivisione:

    1. Crea un ActivityFilter che identifichi un'attività che deve occupare sempre l'intera area di visualizzazione delle attività:

      Kotlin

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

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. Aggiungi il filtro a un insieme di filtri:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. Crea un ActivityRule:

      Kotlin

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

      Java

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

      ActivityRule.Builder crea e configura la regola:

      • expandedActivityFilterSet: contiene filtri attività che determinano quando applicare la regola identificando le attività da escludere dalle suddivisioni.
      • setAlwaysExpand(): specifica se l'attività deve riempire l'intera finestra delle attività.
    4. Aggiungi la regola a WindowManager RuleController:

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

Incorporamento tra applicazioni

Su Android 13 (livello API 33) e versioni successive, le app possono incorporare attività di altre app. L'inserimento di attività cross-application o cross-UID consente l'integrazione visiva delle attività di più applicazioni Android. Il sistema mostra sullo schermo un'attività dell'app host e un'attività incorporata di un'altra app una accanto all'altra o in alto e in basso, come nell'inserimento di attività di una singola app.

Ad esempio, l'app Impostazioni potrebbe incorporare l'attività di selezione dello sfondo dall'app WallpaperPicker:

Figura 14. App Impostazioni (menu a sinistra) con il selettore di sfondo come attività incorporata (a destra).

Modello di attendibilità

I processi host che incorporano attività di altre app sono in grado di ridefinire la presentazione delle attività incorporate, incluse dimensioni, posizione, ritaglio e trasparenza. Gli host malintenzionati possono utilizzare questa funzionalità per ingannare gli utenti e creare clickjacking o altri attacchi di modifica dell'interfaccia utente.

Per evitare l'uso improprio dell'embedding delle attività tra app, Android richiede alle app di attivare la funzionalità per consentire l'embedding delle loro attività. Le app possono designare gli host come attendibili o non attendibili.

Host attendibili

Per consentire ad altre applicazioni di incorporare e controllare completamente la presentazione delle attività della tua app, specifica il certificato SHA-256 dell'applicazione host nell'attributo android:knownActivityEmbeddingCerts degli elementi <activity> o <application> del file manifest della tua app.

Imposta il valore di android:knownActivityEmbeddingCerts come stringa:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

In alternativa, per specificare più certificati, un array di stringhe:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

che fa riferimento a una risorsa come la seguente:

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

I proprietari di app possono ottenere un digest del certificato SHA eseguendo il compito GradlesigningReport. Il digest del certificato è l'impronta SHA-256 senza i due punti di separazione. Per saperne di più, vedi Eseguire un report sulla firma e Autentificare il client.

Host non attendibili

Per consentire a qualsiasi app di incorporare le attività della tua app e controllarne la presentazione, specifica l'attributo android:allowUntrustedActivityEmbedding negli elementi <activity> o <application> nel file manifest dell'app, ad esempio:

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

Il valore predefinito dell'attributo è false, il che impedisce l'embedding delle attività tra app.

Autenticazione personalizzata

Per ridurre i rischi di incorporamento di attività non attendibili, crea un meccanismo di autenticazione personalizzato che verifichi l'identità dell'host. Se conosci i certificati dell'host, utilizza la libreria androidx.security.app.authenticator per autenticarti. Se l'organizzatore si autentica dopo aver incorporato la tua attività, puoi visualizzare i contenuti effettivi. In caso contrario, puoi informare l'utente che l'azione non è stata consentita e bloccare i contenuti.

Utilizza il metodo ActivityEmbeddingController#isActivityEmbedded() della libreria WindowManager di Jetpack per verificare se un host ha incorporato la tua attività, ad esempio:

Kotlin

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

Java

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

Limitazione di dimensioni minime

Il sistema Android applica l'altezza e la larghezza minime specificate nell'elemento <layout> del file manifest dell'app alle attività incorporate. Se un'applicazione non specifica l'altezza e la larghezza minime, vengono applicati i valori predefiniti del sistema (sw220dp).

Se l'host tenta di ridimensionare il contenitore incorporato in modo che sia più piccolo del minimo, il contenitore incorporato si espande per occupare l'intero ambito dell'attività.

<activity-alias>

Affinché l'inserimento di attività attendibili o non attendibili funzioni con l'elemento <activity-alias>, android:knownActivityEmbeddingCerts o android:allowUntrustedActivityEmbedding deve essere applicato all'attività di destinazione anziché all'alias. Il criterio che verifica la sicurezza sul server di sistema si basa sui flag impostati sul target, non sull'alias.

Applicazione host

Le applicazioni host implementano l'inserimento di attività cross-app nello stesso modo in cui implementano l'inserimento di attività di singole app. Gli oggetti SplitPairRule e SplitPairFilter o ActivityRule e ActivityFilter specificano le attività incorporate e le suddivisioni della finestra delle attività. Le regole di suddivisione sono definite staticamente in XML o in fase di esecuzione utilizzando le chiamate API WindowManager di Jetpack.

Se un'applicazione host tenta di incorporare un'attività che non ha attivato l'inserimento in più app, l'attività occupa l'intero ambito dell'attività. Di conseguenza, le applicazioni host devono sapere se le attività target consentono l'embedding tra app.

Se un'attività incorporata avvia una nuova attività nella stessa attività e la nuova attività non ha attivato l'incorporamento tra app, l'attività occupa l'intero ambito dell'attività anziché sovrapporla nel contenitore incorporato.

Un'applicazione host può incorporare le proprie attività senza restrizioni, a condizione che le attività vengano avviate nella stessa attività.

Esempi di suddivisione

Suddividere lo schermo dalla finestra intera

Figura 15. L'attività A avvia l'attività B a lato.

Non è necessario il refactoring. Puoi definire la configurazione della suddivisione staticamente o in fase di esecuzione e poi chiamare Context#startActivity() senza parametri aggiuntivi.

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

Suddiviso per impostazione predefinita

Quando la pagina di destinazione di un'applicazione è progettata per essere suddivisa in due contenitori su schermi di grandi dimensioni, l'esperienza utente è ottimale quando entrambe le attività vengono create e presentate contemporaneamente. Tuttavia, i contenuti potrebbero non essere disponibili per il contenitore secondario della suddivisione finché l'utente non interagisce con l'attività nel contenitore principale (ad esempio, seleziona un elemento da un menu di navigazione). Un'attività segnaposto può colmare il vuoto fino a quando i contenuti non possono essere visualizzati nel contenitore secondario della suddivisione (consulta la sezione Segnaposto).

Figura 16. Separazione creata aprendo due attività contemporaneamente. Un'attività è un segnaposto.

Per creare una suddivisione con un segnaposto, crea un segnaposto e associalo all'attività principale:

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

Quando un'app riceve un'intent, l'attività target può essere mostrata come parte secondaria di una suddivisione dell'attività, ad esempio una richiesta di visualizzazione di una schermata dettagliata con informazioni su un elemento di un elenco. Sui display piccoli, i dettagli vengono visualizzati nella finestra completa dell'attività; sui dispositivi più grandi, accanto all'elenco.

Figura 17. Attività dei dettagli dei link diretti mostrata da sola su uno schermo piccolo, ma insieme a un'attività di elenco su uno schermo di grandi dimensioni.

La richiesta di lancio deve essere indirizzata all'attività principale e l'attività dettagli target deve essere lanciata in un'organizzazione in batch. Il sistema sceglie automaticamente la presentazione corretta, affiancata o in pila, in base alla larghezza di visualizzazione disponibile.

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

La destinazione del link diretto potrebbe essere l'unica attività che deve essere disponibile per l'utente nella pila di navigazione a ritroso e ti consigliamo di evitare di chiudere l'attività dei dettagli e lasciare solo l'attività principale:

Display di grandi dimensioni con attività di elenco e attività dettagliate affiancate.
          La navigazione a ritroso non riesce a ignorare l&#39;attività dei dettagli e a lasciare sullo schermo l&#39;attività dell&#39;elenco.

Display piccolo con solo attività dettagliata. La navigazione a ritroso non riesce a chiudere l&#39;attività dei dettagli e a mostrare l&#39;attività dell&#39;elenco.

Invece, puoi completare entrambe le attività contemporaneamente utilizzando l'attributo finishPrimaryWithSecondary:

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

Consulta la sezione Attributi di configurazione.

Più attività in container suddivisi

L'accatastamento di più attività in un contenitore diviso consente agli utenti di accedere a contenuti approfonditi. Ad esempio, con una suddivisione in elenco e dettagli, l'utente potrebbe dover accedere a una sezione di dettagli secondari, ma mantenere attiva l'attività principale:

Figura 18. L'attività si è aperta nel riquadro secondario della finestra delle attività.

Kotlin

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

Java

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

L'attività di dettaglio secondario viene posizionata sopra l'attività di dettaglio, nascondendola:

L'utente può quindi tornare al livello di dettaglio precedente tornando indietro tramite la serie:

Figura 19. Attività rimossa dalla parte superiore dell'elenco filtri.

L'accatastamento delle attività è il comportamento predefinito quando le attività vengono avviate da un'attività nello stesso contenitore secondario. Anche le attività avviate dal contenitore principale all'interno di una suddivisione attiva vengono inserite nel contenitore secondario nella parte superiore della serie di attività.

Attività in una nuova attività

Quando le attività in una finestra di attività divisa avviano attività in una nuova attività, la nuova attività è separata dall'attività che include la suddivisione e viene visualizzata in una finestra completa. La schermata Recenti mostra due attività: l'attività nella suddivisione e la nuova attività.

Figura 20. Avvia l'attività C in una nuova attività dall'attività B.

Sostituzione dell'attività

Le attività possono essere sostituite nello stack del contenitore secondario, ad esempio quando l'attività principale viene utilizzata per la navigazione di primo livello e l'attività secondaria è una destinazione selezionata. Ogni selezione dal riquadro di navigazione di primo livello deve avviare una nuova attività nel contenitore secondario e rimuovere l'attività o le attività presenti in precedenza.

Figura 21. L'attività di navigazione di primo livello nel riquadro principale sostituisce le attività di destinazione nel riquadro secondario.

Se l'app non completa l'attività nel contenitore secondario quando la selezione di navigazione cambia, la navigazione a ritroso potrebbe creare confusione quando la suddivisione è compressa (quando il dispositivo è piegato). Ad esempio, se hai un menu nel riquadro principale e le schermate A e B impilate nel riquadro secondario, quando l'utente chiude lo smartphone, la schermata B si trova sopra la schermata A e questa sopra il menu. Quando l'utente torna da B, viene visualizzato A anziché il menu.

In questi casi, la schermata A deve essere rimossa dalla pila precedente.

Il comportamento predefinito quando si avvia un nuovo contenitore a lato di una suddivisione esistente è posizionare i nuovi contenitori secondari in alto e mantenere quelli vecchi nella pila posteriore. Puoi configurare le suddivisioni in modo da eliminare i container secondari precedenti con clearTop e avviare nuove attività normalmente.

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

In alternativa, utilizza la stessa attività secondaria e dall'attività principale (menu) invia nuovi intent che risolvono nella stessa istanza, ma attivano un aggiornamento dello stato o dell'interfaccia utente nel contenitore secondario.

Più suddivisioni

Le app possono fornire una navigazione approfondita a più livelli avviando attività aggiuntive sul lato.

Quando un'attività in un contenitore secondario avvia una nuova attività a lato, viene creata una nuova suddivisione sopra quella esistente.

Figura 22. L'attività B avvia l'attività C a lato.

La pila di ritorno contiene tutte le attività aperte in precedenza, quindi gli utenti possono accedere alla suddivisione A/B dopo aver completato C.

Attività A, B e C in uno stack. Le attività sono impilate nel
          seguente ordine, dall&#39;alto verso il basso: C, B, A.

Per creare una nuova suddivisione, avvia la nuova attività a lato del contenitore secondario esistente. Dichiara le configurazioni per le suddivisioni A/B e B/C e avvia normalmente l'attività C da 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));
    }
}

Rispondere alle modifiche dello stato di suddivisione

Attività diverse in un'app possono avere elementi dell'interfaccia utente che svolgono la stessa funzione, ad esempio un controllo che apre una finestra contenente le impostazioni dell'account.

Figura 23. Attività diverse con elementi dell'interfaccia utente funzionalmente identici.

Se due attività che hanno un elemento dell'interfaccia utente in comune sono in una suddivisione, è ridondante e forse fonte di confusione mostrare l'elemento in entrambe le attività.

Figura 24. Elementi dell'interfaccia utente duplicati nella suddivisione delle attività.

Per sapere quando le attività sono in una suddivisione, controlla il flusso SplitController.splitInfoList o registra un ascoltatore con SplitControllerCallbackAdapter per le modifiche dello stato della suddivisione. Quindi, modifica l'interfaccia utente di conseguenza:

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

Le coroutine possono essere avviate in qualsiasi stato del ciclo di vita, ma in genere vengono avviate nello stato STARTED per risparmiare risorse (per ulteriori informazioni, consulta Utilizzare le coroutine Kotlin con componenti consapevoli del ciclo di vita).

I richiami possono essere effettuati in qualsiasi stato del ciclo di vita, anche quando un'attività viene interrotta. In genere, gli ascoltatori devono essere registrati in onStart() e non registrati in onStop().

Modale a finestra intera

Alcune attività impediscono agli utenti di interagire con l'applicazione finché non viene eseguita un'azione specifica, ad esempio un'attività della schermata di accesso, una schermata di conferma delle norme o un messaggio di errore. Le attività modali devono essere impedite da comparire in una suddivisione.

È possibile forzare un'attività a riempire sempre la finestra delle attività utilizzando la configurazione di espansione:

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

Completare le attività

Gli utenti possono completare le attività su entrambi i lati della suddivisione scorrendo dal bordo del display:

Figura 25. Gesto di scorrimento per completare l'attività B.
Figura 26. Gesto di scorrimento che completa l'attività A.

Se il dispositivo è configurato per utilizzare il pulsante Indietro anziché la navigazione con i gesti, l'input viene inviato all'attività attiva, ovvero l'attività toccata o avviata per ultima.

L'effetto della terminazione di tutte le attività in un contenitore sul contenitore opposto dipende dalla configurazione della suddivisione.

Attributi di configurazione

Puoi specificare gli attributi della regola della coppia di suddivisione per configurare in che modo il completamento di tutte le attività su un lato della suddivisione influisce sulle attività sull'altro lato della suddivisione. Gli attributi sono:

  • window:finishPrimaryWithSecondary - In che modo il completamento di tutte le attività nel contenitore secondario influisce sulle attività nel contenitore principale
  • window:finishSecondaryWithPrimary - In che modo il completamento di tutte le attività nel contenitore principale influisce sulle attività nel contenitore secondario

I valori possibili degli attributi includono:

  • always: completa sempre le attività nel contenitore associato
  • never: non completare mai le attività nel contenitore associato
  • adjacent: completa le attività nel contenitore associato quando i due contenitori sono visualizzati uno accanto all'altro, ma non quando sono impilati

Ad esempio:

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

Configurazione predefinita

Quando tutte le attività in un contenitore di una suddivisione terminano, il contenitore rimanente occupa l'intera finestra:

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

Suddivisione contenente le attività A e B. A è terminata, quindi B occupa
          l&#39;intera finestra.

Suddivisione contenente le attività A e B. La finestra B è stata chiusa e A occupa l&#39;intera finestra.

Completare le attività insieme

Termina automaticamente le attività nel contenitore principale quando tutte le attività nel contenitore secondario sono terminate:

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

Suddivisione contenente le attività A e B. L&#39;attività B è stata completata, quindi anche l&#39;attività A è stata completata, lasciando vuota la finestra delle attività.

Suddivisione contenente le attività A e B. L&#39;attività A è stata completata, quindi rimane solo B
          nella finestra delle attività.

Completa automaticamente le attività nel contenitore secondario al termine di tutte le attività nel contenitore principale:

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

Suddivisione contenente le attività A e B. L&#39;attività A è stata completata, quindi anche l&#39;attività B è stata completata, lasciando vuota la finestra delle attività.

Suddivisione contenente le attività A e B. L&#39;attività B è stata completata, quindi rimane solo l&#39;attività A nella finestra delle attività.

Terminare le attività insieme al termine di tutte le attività nel contenitore principale o secondario:

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

Suddivisione contenente le attività A e B. L&#39;attività A è stata completata, quindi anche l&#39;attività B è stata completata, lasciando vuota la finestra delle attività.

Suddivisione contenente le attività A e B. L&#39;attività B è stata completata, quindi anche l&#39;attività A è stata completata, lasciando vuota la finestra delle attività.

Completare più attività nei contenitori

Se in un contenitore diviso sono impilate più attività, il completamento di un'attività nella parte inferiore della pila non completa automaticamente le attività in alto.

Ad esempio, se nel contenitore secondario sono presenti due attività, C sopra B:

Lo stack di attività secondario contenente l&#39;attività C sovrapposto a B
          è sovrapposto allo stack di attività principale contenente l&#39;attività
          A.

e la configurazione della suddivisione è definita dalla configurazione delle attività A e B:

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

se completi l'attività principale, la suddivisione viene mantenuta.

Suddiviso con l&#39;attività A nel contenitore principale e le attività B e C nel contenitore secondario, C sovrapposto a B. C termina, lasciando A e B nella suddivisione delle attività.

Il completamento dell'attività di base (principale) del contenitore secondario non rimuove le attività al di sopra e, di conseguenza, mantiene anche la suddivisione.

Suddiviso con l&#39;attività A nel contenitore principale e le attività B e C nel contenitore secondario, C sovrapposto a B. B termina, lasciando A e C nella suddivisione delle attività.

Vengono eseguite anche eventuali regole aggiuntive per completare le attività insieme, ad esempio completare l'attività secondaria con quella principale:

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

Suddivisione con l&#39;attività A nel contenitore principale e le attività B e C nel contenitore secondario, C sovrapposto a B. A termina, completando anche B e C.

E quando la suddivisione è configurata per completare la primaria e la secondaria insieme:

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

Suddiviso con l&#39;attività A nel contenitore principale e le attività B e C nel contenitore secondario, C sovrapposto a B. C termina, lasciando A e B nella suddivisione delle attività.

Suddiviso con l&#39;attività A nel contenitore principale e le attività B e C nel contenitore secondario, C sovrapposto a B. B termina, lasciando A e C nella suddivisione delle attività.

Suddiviso con l&#39;attività A nel contenitore principale e le attività B e C nel contenitore secondario, C sovrapposto a B. A termina, completando anche B e C.

Modificare le proprietà di suddivisione in fase di esecuzione

Le proprietà di una segmentazione attiva e visibile non possono essere modificate. La modifica delle regole di suddivisione influisce su nuovi lanci di attività e nuovi contenitori, ma non sulle suddivisioni esistenti e attive.

Per modificare le proprietà delle suddivisioni attive, completa l'attività o le attività secondarie nella suddivisione e riavvia la visualizzazione laterale con una nuova configurazione.

Proprietà di suddivisione dinamica

Android 15 (livello API 35) e versioni successive supportate da Jetpack WindowManager 1.4 e versioni successive offrono funzionalità dinamiche che consentono la configurabilità delle suddivisioni di embedding delle attività, tra cui:

  • Espansione dei riquadri:un divisore interattivo e trascinabile consente agli utenti di ridimensionare i riquadri in una presentazione divisa.
  • Fissare la serie di attività:gli utenti possono fissare i contenuti in un contenitore e isolare la navigazione in questo contenitore da quella nell'altro contenitore.
  • Diminuzione della luminosità a schermo intero della finestra di dialogo: quando viene visualizzata una finestra di dialogo, le app possono specificare se attenuare l'intera finestra dell'attività o solo il contenitore che ha aperto la finestra di dialogo.

Espansione del riquadro

L'espansione del riquadro consente agli utenti di regolare la quantità di spazio sullo schermo assegnata alle due attività in un layout a due riquadri.

Per personalizzare l'aspetto del divisore della finestra e impostarne l'intervallo scorrevole:

  1. Crea un'istanza di DividerAttributes

  2. Personalizza gli attributi del divisore:

    • color: il colore del separatore del riquadro trascinatile.

    • widthDp: la larghezza del separatore del riquadro trascinatile. Imposta su WIDTH_SYSTEM_DEFAULT per consentire al sistema di determinare la larghezza del separatore.

    • Intervallo di trascinamento:la percentuale minima dello schermo che ciascun riquadro può occupare. Può variare da 0,33 a 0,66. Imposta su DRAG_RANGE_SYSTEM_DEFAULT per consentire al sistema di determinare l'intervallo di trascinamento.

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

Blocco della pila di attività

La funzionalità di blocco della serie di attività consente agli utenti di bloccare una delle finestre divise in modo che l'attività rimanga invariata mentre gli utenti navigano nell'altra finestra. La funzionalità di bloccamento della serie attività offre un'esperienza di multitasking avanzata.

Per attivare il bloccaggio della pila di attività nella tua app:

  1. Aggiungi un pulsante al file di layout dell'attività che vuoi bloccare, ad esempio l'attività di dettaglio di un layout elenco dettagliato:

    <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. Nel metodo onCreate() dell'attività, imposta un listener onclick sul pulsante:

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

Diminuzione della luminosità a schermo intero della finestra di dialogo

In genere, le attività attenuano i display per attirare l'attenzione su una finestra di dialogo. Per un'esperienza UI unificata, quando viene incorporata un'attività, entrambi i riquadri del display a doppio riquadro devono essere attenuati, non solo il riquadro contenente l'attività che ha aperto la finestra di dialogo.

Con WindowManager 1.4 e versioni successive, l'intera finestra dell'app si attenua per impostazione predefinita quando si apre una finestra di dialogo (vedi EmbeddingConfiguration.DimAreaBehavior.ON_TASK).

Per attenuare solo il contenitore dell'attività che ha aperto la finestra di dialogo, usa EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.

Estrarre un'attività da una finestra divisa a una finestra completa

Crea una nuova configurazione che mostri la finestra completa dell'attività secondaria, quindi riavvia l'attività con un'intenzione che risolva nella stessa istanza.

Verificare il supporto della suddivisione in fase di esecuzione

L'inserimento di attività è supportato su Android 12L (livello API 32) e versioni successive, ma è anche disponibile su alcuni dispositivi con versioni precedenti della piattaforma. Per verificare la disponibilità della funzionalità in fase di esecuzione, utilizza la proprietà SplitController.splitSupportStatus o il metodo 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.
}

Se le suddivisioni non sono supportate, le attività vengono avviate sopra la serie di attività (secondo il modello di embedding non di attività).

Impedire l'override del sistema

I produttori di dispositivi Android (produttori di apparecchiature originali o OEM) possono implementare l'inserimento di attività come funzione del sistema del dispositivo. Il sistema specifica le regole di suddivisione per le app con più attività, sostituendo il comportamento di gestione delle finestre delle app. L'override del sistema forza le app con più attività in una modalità di incorporamento delle attività definita dal sistema.

L'inserimento di attività di sistema può migliorare la presentazione dell'app tramite layout con più riquadri, ad esempio elenco-dettaglio, senza apportare modifiche all'app. Tuttavia, l'inserimento di attività del sistema potrebbe anche causare layout dell'app errati, bug o conflitti con l'inserimento di attività implementato dall'app.

L'app può impedire o consentire l'inserimento di attività di sistema impostando una proprietà nel file manifest dell'app, ad esempio:

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

Il nome della proprietà è definito nell'oggetto WindowManager WindowProperties di Jetpack. Imposta il valore su false se la tua app implementa l'inserimento di attività o se vuoi impedire al sistema di applicare le sue regole di inserimento di attività alla tua app. Imposta il valore su false per consentire al sistema di applicare all'app l'inserimento di attività definito dal sistema.true

Limitazioni, restrizioni e avvertenze

  • Solo l'app host dell'attività, identificata come proprietaria dell'attività principale nell'attività, può organizzare e incorporare altre attività nell'attività. Se le attività che supportano l'inserimento e le suddivisioni vengono eseguite in un'attività appartenente a un'altra applicazione, l'inserimento e le suddivisioni non funzioneranno per queste attività.
  • Le attività possono essere organizzate solo all'interno di un'unica attività. L'avvio di un'attività in una nuova attività la inserisce sempre in una nuova finestra espansa al di fuori di eventuali suddivisioni esistenti.
  • Solo le attività nello stesso processo possono essere organizzate e suddivise. Il callback SplitInfo registra solo le attività che appartengono allo stesso processo, poiché non è possibile conoscere le attività in processi diversi.
  • Ogni coppia o singola regola di attività si applica solo ai lanci di attività che si verificano dopo la registrazione della regola. Al momento non è possibile aggiornare le suddivisioni esistenti o le relative proprietà visive.
  • La configurazione del filtro delle coppie divise deve corrispondere alle intenzioni utilizzate per il lancio completo delle attività. La corrispondenza avviene nel momento in cui viene avviata una nuova attività dal processo di applicazione, pertanto potrebbe non essere a conoscenza dei nomi dei componenti che vengono risolti in un secondo momento nel processo di sistema quando si utilizzano intent impliciti. Se il nome di un componente non è noto al momento del lancio, è possibile utilizzare un carattere jolly ("*/*") e applicare un filtro in base all'azione dell'intent.
  • Al momento non è possibile spostare le attività tra i contenitori o all'interno e all'esterno delle suddivisioni dopo la loro creazione. Le suddivisioni vengono create dalla raccolta WindowManager solo quando vengono avviate nuove attività con regole di corrispondenza e vengono distrutte al termine dell'ultima attività in un contenitore suddiviso.
  • Le attività possono essere riavviate quando la configurazione cambia, quindi quando viene creata o rimossa una suddivisione e i limiti dell'attività cambiano, l'attività può essere completamente distrutta e creata una nuova. Di conseguenza, gli sviluppatori di app devono fare attenzione a operazioni come il lancio di nuove attività dai callback del ciclo di vita.
  • I dispositivi devono includere l'interfaccia delle estensioni della finestra per supportare l'embedding delle attività. Quasi tutti i dispositivi con schermo di grandi dimensioni con Android 12L (livello API 32) o versioni successive includono l'interfaccia. Tuttavia, alcuni dispositivi con schermi di grandi dimensioni che non sono in grado di eseguire più attività non includono l'interfaccia delle estensioni della finestra. Se un dispositivo con schermo di grandi dimensioni non supporta la modalità multi-finestra, potrebbe non supportare l'inserimento di attività.

Risorse aggiuntive