L'incorporamento delle 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à.
Se la tua app è composta da più attività, l'incorporamento delle attività ti consente di offrire un'esperienza utente migliorata su tablet, dispositivi pieghevoli e ChromeOS.
L'incorporamento delle attività non richiede il refactoring del codice. Determina la modalità di visualizzazione delle attività dell'app, affiancate o impilate, creando un file di configurazione XML o effettuando chiamate API Jetpack WindowManager.
Il supporto per gli schermi di piccole dimensioni viene gestito automaticamente. Quando la tua app è su un dispositivo con uno schermo piccolo, le attività sono 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 richiedere una logica di ramificazione.
L'incorporamento delle attività si adatta ai cambiamenti di orientamento del dispositivo e funziona perfettamente sui dispositivi pieghevoli, impilando e separando le attività quando il dispositivo si piega e si apre.
L'incorporamento delle attività è supportato sulla maggior parte dei dispositivi con schermi di grandi dimensioni con Android 12L (livello API 32) e versioni successive.
Finestra dell'attività divisa
L'incorporamento delle attività divide la finestra delle attività dell'app in due container: principale e secondario. I contenitori contengono attività avviate dall'attività principale o da altre attività già presenti nei contenitori.
Le attività vengono impilate nel contenitore secondario man mano che vengono avviate e il contenitore secondario viene impilato sopra il contenitore principale sugli schermi piccoli, quindi l'impilamento delle attività e la navigazione indietro sono coerenti con l'ordine delle attività già integrate nell'app.
L'incorporamento delle attività ti consente di visualizzarle in vari modi. La tua app può dividere la finestra dell'attività avviando due attività contemporaneamente una accanto all'altra o una sopra l'altra:
Un'attività che occupa l'intera finestra dell'attività può creare una divisione avviando una nuova attività contemporaneamente:
Le attività già in una finestra di divisione e condivisione di un'attività possono essere avviate in altri modi:
Di lato, sopra un'altra attività:
Figura 4. L'attività A avvia l'attività C lateralmente rispetto all'attività B. Di lato e sposta la divisione lateralmente, nascondendo l'attività principale precedente:
Figura 5. L'attività B avvia l'attività C lateralmente e sposta la divisione lateralmente. Avvia un'attività in primo piano, ovvero nella stessa pila di attività:
Figura 6. L'attività B avvia l'attività C senza flag di intent aggiuntivi. Avvia un'attività a schermo intero nella stessa attività:
Figura 7. L'attività A o l'attività B avvia l'attività C che riempie la finestra dell'attività.
Navigazione a ritroso
I diversi tipi di applicazioni possono avere regole di navigazione indietro diverse in uno stato della finestra di divisione delle attività a seconda delle dipendenze tra le attività o del modo in cui gli utenti attivano l'evento Indietro, ad esempio:
- Andare insieme: se le attività sono correlate e una non deve essere mostrata senza l'altra, la navigazione indietro può essere configurata per completarle entrambe.
- Se le attività sono completamente indipendenti, la navigazione indietro in un'attività non influisce sullo stato di un'altra attività nella finestra delle attività.
L'evento Indietro viene inviato all'ultima attività selezionata 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 dello schermo, l'evento Indietro viene inviato all'attività nel riquadro a 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 termina 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à in primo piano, in linea con il comportamento della navigazione tramite pulsanti.
Layout a più riquadri
Jetpack WindowManager ti consente di creare un layout multi-riquadro con incorporamento di attività su dispositivi con schermi di grandi dimensioni 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 layout basati su fragment o 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 elenco-dettagli. Per garantire una presentazione di alta qualità, il sistema avvia l'attività di elenco, quindi l'applicazione avvia immediatamente l'attività di dettaglio. Il sistema di transizione attende che vengano disegnate entrambe le attività, quindi le visualizza insieme. Per l'utente, le due attività vengono avviate come una sola.
Attributi di suddivisione
Puoi specificare la proporzione della finestra dell'attività tra i contenitori suddivisi e la disposizione dei contenitori l'uno rispetto 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 contenitori suddivisi l'uno rispetto all'altro. I valori includono:ltr: da sinistra a destrartl: da destra a sinistralocale:ltrortlviene determinato dall'impostazione delle impostazioni internazionali
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 del builder:
setSplitType(): imposta le proporzioni dei contenitori suddivisi. ConsultaSplitAttributes.SplitTypeper gli argomenti validi, incluso il metodoSplitAttributes.SplitType.ratio().setLayoutDirection(): imposta il layout dei contenitori. VediSplitAttributes.LayoutDirectionper i valori possibili.
Per esempi, consulta la sezione API WindowManager.
Orientamento della suddivisione
Le dimensioni e le proporzioni del display determinano il posizionamento delle attività nelle suddivisioni dell'incorporamento delle attività. Sui display orizzontali di grandi dimensioni, le attività vengono visualizzate una accanto all'altra; sui display verticali o in modalità tavolo sui dispositivi pieghevoli, una sopra l'altra.
Puoi specificare l'orientamento della suddivisione con il SplitController
SplitAttributes. Il calcolatore calcola SplitAttributes per
l'SplitRule attivo.
Utilizza il calcolatore per dividere il contenitore principale in direzioni diverse per diversi stati del dispositivo, ad esempio:
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator { params -> val parentConfiguration = params.parentConfiguration val builder = SplitAttributes.Builder() return@setSplitAttributesCalculator if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build() } else if (parentConfiguration.screenHeightDp >= 600) { // Horizontal split for tall displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) .build() } else { // Fallback to expand the secondary container. builder .setSplitType(SPLIT_TYPE_EXPAND) .build() } } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator(params -> { Configuration parentConfiguration = params.getParentConfiguration(); SplitAttributes.Builder builder = new SplitAttributes.Builder(); if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build(); } else if (parentConfiguration.screenHeightDp >= 600) { // Horizontal split for tall displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) .build(); } else { // Fallback to expand the secondary container. return builder .setSplitType(SplitType.SPLIT_TYPE_EXPAND) .build(); } }); }
Sui dispositivi pieghevoli, puoi dividere lo schermo verticalmente se il dispositivo è in orizzontale, visualizzare una singola attività se il dispositivo è in verticale e dividere lo schermo orizzontalmente se il dispositivo è in modalità Tabletop:
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator { params -> val tag = params.splitRuleTag val parentWindowMetrics = params.parentWindowMetrics val parentConfiguration = params.parentConfiguration val foldingFeatures = params.parentWindowLayoutInfo.displayFeatures.filterIsInstance<FoldingFeature>() val feature = if (foldingFeatures.size == 1) foldingFeatures[0] else null val builder = SplitAttributes.Builder() builder.setSplitType(SPLIT_TYPE_HINGE) return@setSplitAttributesCalculator if (feature?.isSeparating == true) { // Horizontal split for tabletop posture. builder .setSplitType(SPLIT_TYPE_HINGE) .setLayoutDirection( if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL) { SplitAttributes.LayoutDirection.BOTTOM_TO_TOP } else { SplitAttributes.LayoutDirection.LOCALE } ) .build() } else if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build() } else { // No split for tall displays. builder .setSplitType(SPLIT_TYPE_EXPAND) .build() } } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator(params -> { String tag = params.getSplitRuleTag(); WindowMetrics parentWindowMetrics = params.getParentWindowMetrics(); Configuration parentConfiguration = params.getParentConfiguration(); List<FoldingFeature> foldingFeatures = params.getParentWindowLayoutInfo().getDisplayFeatures().stream().filter( item -> item instanceof FoldingFeature) .map(item -> (FoldingFeature) item) .collect(Collectors.toList()); FoldingFeature feature = foldingFeatures.size() == 1 ? foldingFeatures.get(0) : null; SplitAttributes.Builder builder = new SplitAttributes.Builder(); builder.setSplitType(SplitType.SPLIT_TYPE_HINGE); if (feature != null && feature.isSeparating()) { // Horizontal slit for tabletop posture. return builder .setSplitType(SplitType.SPLIT_TYPE_HINGE) .setLayoutDirection( feature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL ? SplitAttributes.LayoutDirection.BOTTOM_TO_TOP : SplitAttributes.LayoutDirection.LOCALE) .build(); } else if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build(); } else { // No split for tall displays. return builder .setSplitType(SplitType.SPLIT_TYPE_EXPAND) .build(); } }); }
Segnaposto
Le attività segnaposto sono attività secondarie vuote che occupano un'area di una divisione dell'attività. In definitiva, devono essere sostituite con un'altra attività che contenga contenuti. Ad esempio, un'attività segnaposto potrebbe occupare il lato secondario di una divisione dell'attività in un layout elenco-dettagli finché non viene selezionato un elemento dall'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 c'è spazio sufficiente per una divisione dell'attività. I segnaposto vengono completati automaticamente quando le dimensioni del display cambiano in una larghezza o un'altezza troppo piccole per visualizzare una divisione. Quando lo spazio lo consente, il sistema riavvia il segnaposto con uno stato reinizializzato.
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 di true, il
sistema visualizza il segnaposto come attività principale nella finestra delle attività quando
il display viene ridimensionato a un display a un solo riquadro da un display a due riquadri
(vedi Configurazione split per un esempio).
Modifiche alle dimensioni della finestra
Quando le modifiche alla configurazione del dispositivo riducono la larghezza della finestra dell'attività in modo che non sia abbastanza 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 quando la finestra dell'app viene ridimensionata in modalità multi-finestra), le attività non segnaposto nel riquadro secondario della finestra dell'attività vengono impilate sopra le attività nel riquadro principale.
Le attività segnaposto vengono visualizzate solo quando la larghezza del display è sufficiente per una divisione. Sugli schermi più piccoli, il segnaposto viene chiuso automaticamente. Quando l'area di visualizzazione diventa di nuovo sufficientemente grande, il segnaposto viene ricreato. (Vedi la sezione Segnaposto.)
L'impilamento 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 loco senza flag di intent aggiuntivi:

con il seguente ordine di visualizzazione delle attività nella stessa attività:

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

Tornare indietro nella finestra più piccola consente di scorrere le attività impilate una sopra l'altra.
Se la configurazione della finestra delle attività viene ripristinata a una dimensione maggiore in grado di ospitare più riquadri, le attività vengono visualizzate di nuovo una accanto all'altra.
Divisioni in pila
L'attività B avvia l'attività C di lato e sposta la divisione lateralmente:

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

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

Orientamento verticale fisso
L'impostazione del manifest android:screenOrientation consente alle app di limitare le attività all'orientamento verticale o orizzontale. Per migliorare l'esperienza utente sui dispositivi con schermi di grandi dimensioni, come tablet e pieghevoli, i produttori di dispositivi (OEM) possono ignorare le richieste di orientamento dello schermo e aggiungere barre nere all'app in orientamento verticale sui display orizzontali o in orientamento orizzontale sui display verticali.
Allo stesso modo, quando l'incorporamento delle attività è attivato, gli OEM possono personalizzare i dispositivi per letterbox attività con orientamento verticale fisso in orientamento orizzontale su schermi di grandi dimensioni (larghezza ≥ 600 dp). Quando un'attività con orientamento verticale fisso avvia una seconda attività, il dispositivo può visualizzare le due attività affiancate in una visualizzazione a due riquadri.
Aggiungi sempre la proprietà android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
al file manifest dell'app per comunicare ai dispositivi che la tua app supporta
l'incorporamento di attività (vedi la sezione Configurazione split). I dispositivi personalizzati dall'OEM possono quindi determinare se aggiungere barre nere
alle attività in formato verticale fisso.
Configurazione della suddivisione
Le regole di divisione configurano le divisioni delle attività. Definisci le regole di suddivisione in un file di configurazione XML o effettuando chiamate API WindowManager di Jetpack.
In entrambi i casi, l'app deve accedere alla libreria WindowManager e deve comunicare al sistema che ha implementato l'incorporamento delle attività.
Esegui le seguenti azioni:
Aggiungi la dipendenza della libreria WindowManager più recente al file
build.gradlea 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'incorporamento delle attività.
Comunica al sistema che la tua app ha implementato l'incorporamento di attività.
Aggiungi la proprietà
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLEDall'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>Nelle versioni 1.1.0-alpha06 e successive di WindowManager, le suddivisioni dell'incorporamento 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'incorporamento di attività. Ad esempio, i dispositivi possono inserire una barra nera in alto e in basso in un'attività solo verticale sui display orizzontali per orientare l'attività per la transizione a un layout a due riquadri quando inizia una seconda attività (vedi Orientamento verticale fisso).
Configurazione XML
Per creare un'implementazione basata su XML dell'incorporamento dell'attività, completa i seguenti passaggi:
Crea un file di risorse XML che:
- Definisce le attività che condividono una divisione
- 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 divisione
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>Crea un inizializzatore.
Il componente WindowManager
RuleControlleranalizza il file di configurazione XML e rende disponibili le regole per il sistema. Una libreria Startup di JetpackInitializerrende disponibile il file XML aRuleControllerall'avvio dell'app, in modo che le regole siano effettive all'avvio di qualsiasi attività.Per creare un inizializzatore:
Aggiungi la dipendenza della libreria Jetpack Startup più recente al file
build.gradlea livello di modulo, ad esempio:implementation 'androidx.startup:startup-runtime:1.1.1'Crea una classe che implementi l'interfaccia
Initializer.L'inizializzatore rende disponibili le regole di suddivisione per
RuleControllerpassando l'ID del file di configurazione XML (main_split_config.xml) al metodoRuleController.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(); } }
Crea un fornitore di contenuti per le definizioni delle regole.
Aggiungi
androidx.startup.InitializationProvideral file manifest dell'app come<provider>. Includi un riferimento all'implementazione dell'inizializzatoreRuleController,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>InitializationProviderrileva e inizializzaSplitInitializerprima che venga chiamato il metodoonCreate()dell'app. Di conseguenza, le regole di suddivisione sono attive all'avvio dell'attività principale dell'app.
API WindowManager
Puoi implementare l'incorporamento delle attività in modo programmatico con una serie di chiamate API. Esegui le chiamate nel metodo onCreate() di una sottoclasse di
Application per assicurarti che le regole siano in vigore prima dell'avvio di qualsiasi attività.
Per creare una divisione dell'attività a livello di programmazione:
Crea una regola di suddivisione:
Crea un
SplitPairFilterche 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 );
Aggiungi il filtro a un insieme di filtri:
```Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
Crea gli attributi di layout per la divisione:
Kotlin
val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build()
Java
SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
SplitAttributes.Buildercrea un oggetto contenente gli attributi del layout:setSplitType(): definisce come viene allocata l'area di visualizzazione disponibile a ciascun contenitore di attività. Il tipo di suddivisione del rapporto specifica la proporzione dell'area di visualizzazione disponibile allocata al contenitore principale; il contenitore secondario occupa la parte restante dell'area di visualizzazione disponibile.setLayoutDirection(): specifica la disposizione dei contenitori delle attività uno rispetto all'altro, con il contenitore principale per primo.
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.Buildercrea e configura la regola:filterSet: contiene filtri di coppia di suddivisione 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 divisione.setMinSmallestWidthDp(): imposta il valore minimo (in dp) che la più piccola delle due dimensioni del display deve avere per attivare una divisione indipendentemente dall'orientamento del dispositivo.setMaxAspectRatioInPortrait(): imposta le proporzioni di visualizzazione massime (altezza:larghezza) in orientamento verticale per le quali vengono visualizzate le suddivisioni dell'attività. Se le proporzioni di un display 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 ancheSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTesetMaxAspectRatioInLandscape(). Il valore predefinito per il formato orizzontale èALWAYS_ALLOW.setFinishPrimaryWithSecondary(): imposta il modo in cui il completamento di tutte le attività nel contenitore secondario influisce sulle attività nel contenitore principale.NEVERindica che il sistema non deve terminare le attività principali quando tutte le attività nel contenitore secondario terminano (vedi Termina attività).setFinishSecondaryWithPrimary(): imposta il modo in cui il completamento di tutte le attività nel contenitore principale influisce sulle attività nel contenitore secondario.ALWAYSindica che il sistema deve sempre terminare le attività nel contenitore secondario quando tutte le attività nel contenitore principale sono terminate (vedi Terminare le attività).setClearTop(): specifica se tutte le attività nel container secondario sono terminate quando viene avviata una nuova attività nel container. Un valorefalsespecifica che le nuove attività vengono inserite in pila sopra le attività già presenti nel contenitore secondario.
Ottieni 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);
Crea un segnaposto per il contenitore secondario quando il contenuto non è disponibile:
Crea un
ActivityFilterche identifichi l'attività con cui il segnaposto condivide una suddivisione della finestra dell'attività:Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
Aggiungi il filtro a un insieme di filtri:
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
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(this, PlaceholderActivity.class) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build();
SplitPlaceholderRule.Buildercrea e configura la regola:placeholderActivityFilterSet: contiene i filtri attività che determinano quando applicare la regola identificando le attività a cui è associata l'attività segnaposto.Intent: specifica l'avvio 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 divisione.setMinSmallestWidthDp(): Imposta il valore minimo (in dp) che la più piccola delle due dimensioni del display deve avere per consentire una divisione indipendentemente dall'orientamento del dispositivo.setMaxAspectRatioInPortrait(): Imposta le proporzioni massime di visualizzazione (altezza:larghezza) in orientamento verticale per cui vengono visualizzate le suddivisioni dell'attività. Nota:il valore predefinito è 1,4, il che fa sì che le attività riempiano la finestra delle attività in orientamento verticale sulla maggior parte dei tablet. Vedi ancheSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTesetMaxAspectRatioInLandscape(). Il valore predefinito per il formato orizzontale èALWAYS_ALLOW.setFinishPrimaryWithPlaceholder(): Imposta il modo in cui il completamento dell'attività segnaposto influisce sulle attività nel contenitore principale. SEMPRE indica che il sistema deve sempre terminare le attività nel contenitore principale quando il segnaposto termina (vedi Termina attività).setSticky(): determina se l'attività segnaposto viene visualizzata in cima alla pila di attività sui display di piccole dimensioni una volta che il segnaposto è apparso per la prima volta in una divisione con una larghezza minima sufficiente.
Aggiungi la regola a WindowManager
RuleController:Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
Specifica le attività che non devono mai far parte di una divisione:
Crea un
ActivityFilterche identifichi un'attività che deve sempre occupare l'intera area di visualizzazione dell'attività:Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
Aggiungi il filtro a un insieme di filtri:
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
Crea un
ActivityRule:Kotlin
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
Java
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
ActivityRule.Buildercrea e configura la regola:expandedActivityFilterSet: contiene filtri attività che determinano quando applicare la regola identificando le attività che vuoi escludere dalle suddivisioni.setAlwaysExpand(): specifica se l'attività deve riempire l'intera finestra dell'attività.
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'incorporamento di attività tra applicazioni diverse o tra UID diversi consente l'integrazione visiva di attività provenienti da più applicazioni Android. Il sistema mostra un'attività dell'app host e un'attività incorporata di un'altra app sullo schermo affiancate o una sopra l'altra, proprio come nell'incorporamento di attività di una singola app.
Ad esempio, l'app Impostazioni potrebbe incorporare l'attività di selezione dello sfondo dall'app WallpaperPicker:
Modello di attendibilità
I processi host che incorporano attività di altre app sono in grado di ridefinire la presentazione delle attività incorporate, tra cui dimensioni, posizione, ritaglio e trasparenza. Gli host dannosi possono utilizzare questa funzionalità per ingannare gli utenti e creare attacchi di clickjacking o di altro tipo di UI redressing.
Per evitare l'uso improprio dell'incorporamento dell'attività cross-app, Android richiede alle app di attivare l'incorporamento delle proprie 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"
... />
oppure, 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 delle app possono ottenere un digest del certificato SHA eseguendo l'attività Gradle
signingReport. Il digest del certificato è l'impronta SHA-256 senza
i due punti che separano i caratteri. Per saperne di più, vedi Generare un report sulla firma e
Autenticare 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 manifest dell'app, ad esempio:
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
Il valore predefinito dell'attributo è false, che impedisce l'incorporamento dell'attività cross-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 host, utilizza la libreria androidx.security.app.authenticator per l'autenticazione. Se l'organizzatore si autentica dopo l'incorporamento della tua attività, puoi
visualizzare i contenuti effettivi. In caso contrario, puoi comunicare all'utente che l'azione non è stata consentita e bloccare i contenuti.
Utilizza il metodo ActivityEmbeddingController#isActivityEmbedded() della libreria
Jetpack WindowManager per verificare se un host sta incorporando 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(context).isActivityEmbedded(activity); }
Limitazione relativa alle dimensioni minime
Il sistema Android applica l'altezza e la larghezza minime specificate nell'elemento
manifest <layout> 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 a una dimensione inferiore a quella minima, il contenitore incorporato si espande fino a occupare l'intero riquadro dell'attività.
<activity-alias>
Perché l'incorporamento 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 sulla destinazione, non sull'alias.
Applicazione host
Le applicazioni host implementano l'incorporamento dell'attività cross-app nello stesso modo in cui
implementano l'incorporamento dell'attività in una singola 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 runtime utilizzando le chiamate API Jetpack
WindowManager.
Se un'applicazione host tenta di incorporare un'attività che non ha attivato l'incorporamento tra app, l'attività occupa l'intero limite dell'attività. Di conseguenza, le applicazioni host devono sapere se le attività di destinazione consentono l'incorporamento 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 limite dell'attività anziché sovrapporsi all'attività nel contenitore incorporato.
Un'applicazione host può incorporare le proprie attività senza limitazioni, a condizione che le attività vengano avviate nella stessa attività.
Esempi di suddivisione
Dividere lo schermo dalla finestra intera
Non è necessario il refactoring. Puoi definire la configurazione della suddivisione
in modo statico o in fase di runtime e poi chiamare Context#startActivity() senza
parametri aggiuntivi.
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Suddivisione predefinita
Quando la pagina di destinazione di un'applicazione è progettata per essere suddivisa in due contenitori su schermi di grandi dimensioni, l'esperienza utente è migliore 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, l'utente seleziona un elemento da un menu di navigazione). Un'attività segnaposto può riempire il vuoto finché i contenuti non possono essere visualizzati nel contenitore secondario della suddivisione (vedi la sezione 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>
Suddivisione dei link diretti
Quando un'app riceve un intent, l'attività di destinazione può essere mostrata come parte secondaria di una divisione dell'attività, ad esempio una richiesta di visualizzazione di una schermata di dettagli 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.
La richiesta di avvio deve essere indirizzata all'attività principale e l'attività di dettaglio di destinazione deve essere avviata in una suddivisione. Il sistema sceglie automaticamente la presentazione corretta, in pila o affiancata, in base alla larghezza di visualizzazione disponibile.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
La destinazione del link diretto potrebbe essere l'unica attività che dovrebbe essere disponibile per l'utente nello stack di navigazione indietro e potresti voler evitare di chiudere l'attività di dettaglio e lasciare solo l'attività principale:


In alternativa, 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 contenitori suddivisi
L'impilamento di più attività in un contenitore diviso consente agli utenti di accedere a contenuti approfonditi. Ad esempio, con una suddivisione elenco-dettagli, l'utente potrebbe dover accedere a una sezione di dettagli secondari, ma mantenere l'attività principale:
Kotlin
class DetailActivity : AppCompatActivity() { fun onOpenSubdetail() { startActivity(Intent(this, SubdetailActivity::class.java)) } }
Java
public class DetailActivity extends AppCompatActivity { void onOpenSubdetail() { startActivity(new Intent(this, SubdetailActivity.class)); } }
L'attività secondaria viene posizionata sopra l'attività principale, nascondendola:

L'utente può quindi tornare al livello di dettaglio precedente tornando indietro nella pila:
L'impilamento delle attività una sopra l'altra è il comportamento predefinito quando le attività vengono avviate da un'attività nello stesso contenitore secondario. Le attività avviate dal contenitore principale all'interno di una divisione attiva finiscono anche nel contenitore secondario nella parte superiore dello stack 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 da quella che include la divisione e viene visualizzata a schermo intero. La schermata Recenti mostra due attività: l'attività nella divisione e la nuova attività.
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 dalla navigazione di primo livello deve avviare una nuova attività nel contenitore secondario e rimuovere l'attività o le attività che erano presenti in precedenza.
Se l'app non completa l'attività nel contenitore secondario quando la selezione di navigazione cambia, la navigazione indietro potrebbe essere confusa quando la divisione viene compressa (quando il dispositivo è chiuso). 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, B si trova sopra A e A sopra il menu. Quando l'utente torna indietro da B, viene visualizzata A anziché il menu.
In questi casi, la schermata A deve essere rimossa dallo stack precedente.

Il comportamento predefinito quando si avvia un'app lateralmente in un nuovo container su una divisione esistente è quello di posizionare i nuovi container secondari in primo piano e mantenere quelli precedenti nello stack precedente. Puoi configurare le suddivisioni in modo da cancellare i contenitori secondari
precedenti con clearTop e avviare normalmente nuove attività.
<SplitPairRule
window:clearTop="true">
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenA"/>
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>
Kotlin
inner class MenuActivity : AppCompatActivity() { fun onMenuItemSelected(selectedMenuItem: Int) { startActivity(Intent(this, classForItem(selectedMenuItem))) } }
Java
public class MenuActivity extends AppCompatActivity{ void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
In alternativa, utilizza la stessa attività secondaria e dall'attività principale (menu) invia nuovi intent che si risolvono nella stessa istanza, ma attivano un aggiornamento dello stato o dell'interfaccia utente nel contenitore secondario.
Divisioni multiple
Le app possono fornire una navigazione in profondità a più livelli avviando attività aggiuntive di lato.
Quando un'attività in un container secondario avvia una nuova attività lateralmente, viene creata una nuova divisione sopra quella esistente.
Lo stack precedente contiene tutte le attività aperte in precedenza, in modo che gli utenti possano passare alla suddivisione A/B dopo aver terminato C.

Per creare una nuova divisione, avvia la nuova attività lateralmente dal contenitore secondario esistente. Dichiara le configurazioni per le suddivisioni A/B e B/C e avvia l'attività C normalmente da B:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
<SplitPairFilter
window:primaryActivityName=".B"
window:secondaryActivityName=".C"/>
</SplitPairRule>
Kotlin
class B : AppCompatActivity() { fun onOpenC() { startActivity(Intent(this, C::class.java)) } }
Java
public class B extends AppCompatActivity{ void onOpenC() { startActivity(new Intent(this, C.class)); } }
Reagire alle modifiche dello stato di divisione
Diverse attività 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.
Se due attività che hanno un elemento UI in comune sono in una divisione, è ridondante e forse confuso mostrare l'elemento in entrambe le attività.
Per sapere quando le attività sono in una divisione, controlla il flusso
SplitController.splitInfoList o registra un listener con
SplitControllerCallbackAdapter per le modifiche allo stato di divisione. Quindi,
regola la UI di conseguenza:
Kotlin
val layout = layoutInflater.inflate(R.layout.activity_main, null) val view = layout.findViewById<View>(R.id.infoButton) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance. .collect { list -> view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE } } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); new SplitControllerCallbackAdapter(SplitController.getInstance(this)) .addSplitListener( this, Runnable::run, splitInfoList -> { View layout = getLayoutInflater().inflate(R.layout.activity_main, null); layout.findViewById(R.id.infoButton).setVisibility( splitInfoList.isEmpty() ? View.VISIBLE : View.GONE); }); }
Le coroutine possono essere avviate in qualsiasi stato del ciclo di vita, ma in genere vengono avviate nello stato STARTED per risparmiare risorse (per saperne di più, consulta Utilizzare le coroutine Kotlin con componenti sensibili al ciclo di vita).
I callback possono essere eseguiti in qualsiasi stato del ciclo di vita, anche quando un'attività è
interrotta. 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 escluse dalla visualizzazione in una divisione.
Un'attività può essere forzata a riempire sempre la finestra dell'attività utilizzando la configurazione di espansione:
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullWidthActivity"/>
</ActivityRule>
Terminare le attività
Gli utenti possono completare le attività su entrambi i lati della divisione scorrendo dal bordo del display:
Se il dispositivo è configurato per utilizzare il pulsante Indietro anziché la navigazione tramite gesti, l'input viene inviato all'attività selezionata, ovvero l'attività che è stata toccata o avviata per ultima.
L'effetto del completamento di tutte le attività in un container sul container opposto dipende dalla configurazione della divisione.
Attributi di configurazione
Puoi specificare gli attributi della regola di divisione della coppia per configurare in che modo il completamento di tutte le attività di un lato della divisione influisce sulle attività dell'altro lato della divisione. Gli attributi sono:
window:finishPrimaryWithSecondary— In che modo il completamento di tutte le attività nel contenitore secondario influisce sulle attività nel contenitore principalewindow:finishSecondaryWithPrimary: in che modo il completamento di tutte le attività nel container principale influisce sulle attività nel container secondario
I valori possibili degli attributi includono:
always: completa sempre le attività nel contenitore associatonever: non completare mai le attività nel contenitore associatoadjacent: completa le attività nel contenitore associato quando i due contenitori vengono visualizzati uno accanto all'altro, ma non quando sono impilati
Ad esempio:
<SplitPairRule
<!-- Do not finish primary container activities when all secondary container activities finish. -->
window:finishPrimaryWithSecondary="never"
<!-- Finish secondary container activities when all primary container activities finish. -->
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Configurazione predefinita
Quando tutte le attività in un container di una schermata divisa terminano, il container rimanente occupa l'intera finestra:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>


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>


Termina automaticamente le attività nel contenitore secondario quando tutte le attività nel contenitore principale terminano:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>


Termina le attività insieme quando tutte le attività nel contenitore principale o secondario sono terminate:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>


Completare più attività nei container
Se più attività sono impilate in un contenitore diviso, il completamento di un'attività nella parte inferiore della pila non comporta il completamento automatico delle attività nella parte superiore.
Ad esempio, se due attività si trovano nel contenitore secondario, C sopra B:

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à in alto, la divisione viene mantenuta.

Il completamento dell'attività principale (radice) del contenitore secondario non rimuove le attività sopra di essa e quindi mantiene anche la divisione.

Vengono eseguite anche eventuali regole aggiuntive per il completamento delle attività insieme, ad esempio il completamento dell'attività secondaria con quella principale:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>

E quando la suddivisione è configurata per terminare insieme la primaria e la secondaria:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>



Modificare le proprietà di suddivisione in fase di runtime
Le proprietà di una suddivisione attiva e visibile non possono essere modificate. La modifica delle regole di suddivisione influisce su ulteriori avvii di attività e nuovi contenitori, ma non sulle suddivisioni esistenti e attive.
Per modificare le proprietà delle divisioni attive, completa l'attività secondaria o le attività nella divisione e avviala di nuovo lateralmente 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 dell'incorporamento delle attività, tra cui:
- Espansione del riquadro: un divisore interattivo e trascinabile consente agli utenti di ridimensionare i riquadri in una presentazione divisa.
- Blocco della pila di attività:gli utenti possono bloccare i contenuti in un container e isolare la navigazione nel container dalla navigazione nell'altro container.
- Oscuramento a schermo intero della finestra di dialogo:quando viene visualizzata una finestra di dialogo, le app possono specificare se oscurare 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 allocato alle due attività in un layout a due riquadri.
Per personalizzare l'aspetto del divisore della finestra e impostare l'intervallo trascinabile del divisore:
Crea un'istanza di
DividerAttributesPersonalizza gli attributi del divisore:
color: il colore del separatore del riquadro trascinabile.widthDp: la larghezza del separatore del riquadro trascinabile. Imposta suWIDTH_SYSTEM_DEFAULTper consentire al sistema di determinare la larghezza del divisore.Intervallo di trascinamento:la percentuale minima dello schermo che può occupare ciascun riquadro. Può variare da 0,33 a 0,66. Imposta su
DRAG_RANGE_SYSTEM_DEFAULTper 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(R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ) } val splitAttributes: SplitAttributes = splitAttributesBuilder.build()
Java
SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT); if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { splitAttributesBuilder.setDividerAttributes( new DividerAttributes.DraggableDividerAttributes.Builder() .setColor(ContextCompat.getColor(this, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ); } SplitAttributes _splitAttributes = splitAttributesBuilder.build();
Blocco dello stack delle attività
Il blocco della pila 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. Il blocco dello stack di attività offre un'esperienza di multitasking migliorata.
Per attivare il blocco della pila di attività nella tua app:
Aggiungi un pulsante al file di layout dell'attività che vuoi bloccare, ad esempio l'attività di dettaglio di un layout elenco-dettaglio:
<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>Nel metodo
onCreate()dell'attività, imposta un listener onclick sul pulsante:Kotlin
val pinButton: Button = findViewById(R.id.pinButton) pinButton.setOnClickListener { val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build() val pinSplitRule = SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build() SplitController.getInstance(applicationContext) .pinTopActivityStack(taskId, pinSplitRule) }
Java
Button pinButton = findViewById(R.id.pinButton); pinButton.setOnClickListener( (view) -> { SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build(); SplitPinRule pinSplitRule = new SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build(); SplitController.getInstance(getApplicationContext()) .pinTopActivityStack(getTaskId(), pinSplitRule); });
Finestra di dialogo con attenuazione a schermo intero
In genere, le attività oscurano i display per attirare l'attenzione su una finestra di dialogo. Nell'incorporamento dell'attività, entrambi i riquadri della visualizzazione a doppio riquadro devono essere oscurati, non solo il riquadro contenente l'attività che ha aperto la finestra di dialogo, per un'esperienza UI unificata.
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 oscurare solo il contenitore dell'attività che ha aperto la finestra di dialogo, utilizza
EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.
Estrarre un'attività da una finestra divisa a una finestra intera
Crea una nuova configurazione che mostri l'intera finestra dell'attività secondaria, quindi riavvia l'attività con un intent che si risolve nella stessa istanza.
Controllare il supporto della suddivisione in fase di runtime
L'incorporamento delle attività è supportato su Android 12L (livello API 32) e versioni successive, ma è
disponibile anche su alcuni dispositivi con versioni precedenti della piattaforma. Per verificare in fase di runtime la disponibilità della funzionalità, 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 in cima allo stack di attività (seguendo il modello di incorporamento non attività).
Impedire l'override del sistema
I produttori di dispositivi Android (produttori di apparecchiature originali o OEM) possono implementare l'incorporamento delle attività come funzione del sistema del dispositivo. Il sistema specifica le regole di divisione per le app multi-attività, ignorando il comportamento di ridimensionamento delle finestre delle app. L'override del sistema forza le app multi-attività in una modalità di incorporamento delle attività definita dal sistema.
L'incorporamento dell'attività di sistema può migliorare la presentazione dell'app tramite layout multischermo, come elenco-dettagli, senza alcuna modifica all'app. Tuttavia, l'incorporamento dell'attività di sistema potrebbe anche causare layout errati dell'app, bug o conflitti con l'incorporamento dell'attività implementato dall'app.
La tua app può impedire o consentire l'incorporamento dell'attività di sistema impostando
PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE 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 Jetpack WindowManager WindowProperties. Imposta il valore su false se la tua app implementa l'incorporamento di attività o se vuoi impedire al sistema di applicare le regole di incorporamento di attività alla tua app. Imposta il valore su true per consentire al sistema di applicare l'incorporamento di attività definito dal sistema alla tua app.
Limitazioni, restrizioni e avvertenze
- Solo l'app host dell'attività, identificata come proprietaria dell'attività radice, può organizzare e incorporare altre attività nell'attività. Se le attività che supportano l'incorporamento e la suddivisione vengono eseguite in un'attività che appartiene a un'applicazione diversa, l'incorporamento e la suddivisione non funzioneranno per queste attività.
- Le attività possono essere organizzate solo all'interno di una singola attività. L'avvio di un'attività in una nuova attività la inserisce sempre in una nuova finestra espansa al di fuori di eventuali divisioni esistenti.
- Solo le attività nello stesso processo possono essere organizzate e inserite in una suddivisione. Il callback
SplitInfosegnala solo le attività che appartengono allo stesso processo, poiché non è possibile conoscere le attività in processi diversi. - Ogni regola di attività singola o in coppia si applica solo agli avvii di attività che si verificano dopo la registrazione della regola. Al momento non è possibile aggiornare le suddivisioni esistenti o le loro proprietà visive.
- La configurazione del filtro per coppie divise deve corrispondere agli intent utilizzati per avviare completamente le attività. La corrispondenza si verifica quando viene avviata una nuova attività dal processo dell'applicazione, quindi potrebbe non conoscere i 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 il filtro può essere eseguito in base all'azione 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 solo dalla libreria WindowManager quando vengono avviate nuove attività con regole corrispondenti e vengono eliminate quando l'ultima attività in un contenitore di suddivisione è terminata.
- Le attività possono essere riavviate quando la configurazione cambia, quindi quando viene creata o rimossa una divisione e i limiti dell'attività cambiano, l'attività può subire la distruzione completa dell'istanza precedente e la creazione di quella nuova. Di conseguenza, gli sviluppatori di app devono prestare attenzione a elementi come l'avvio di nuove attività dai callback del ciclo di vita.
- I dispositivi devono includere l'interfaccia delle estensioni della finestra per supportare l'incorporamento dell'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 schermo 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'incorporamento delle attività.
Risorse aggiuntive
- Codelab:
- Percorso di apprendimento: incorporamento delle attività
- App di esempio: activity-embedding