Intégration d'activités

L'intégration d'activités permet d'optimiser une application pour les grands écrans en divisant sa fenêtre de tâches en deux activités ou deux instances d'une même activité.

Figure 1 : Application Paramètres affichant des activités côte à côte.

Si votre application se compose de plusieurs activités, l'intégration d'activités vous permet de proposer une expérience utilisateur améliorée sur les tablettes, les pliables et les appareils ChromeOS.

L'intégration d'activités ne nécessite aucune refactorisation du code. Vous déterminez comment votre application affiche ses activités (côte à côte ou empilées) en créant un fichier de configuration XML ou en effectuant des appels d'API Jetpack WindowManager.

Les petits écrans sont gérés automatiquement. Lorsque votre application est installée sur un appareil doté d'un petit écran, les activités sont empilées les unes au-dessus des autres. Sur les grands écrans, les activités sont affichées côte à côte. Le système détermine la présentation en fonction de la configuration que vous avez créée. Aucune logique de ramification n'est nécessaire.

L'intégration d'activités est compatible avec les changements d'orientation des appareils et fonctionne parfaitement sur les appareils pliables. Les activités sont ainsi empilées et dépilées selon que l'appareil est replié ou déplié.

L'intégration d'activités est compatible avec la plupart des appareils à grand écran équipés d'Android 12L (niveau d'API 32) ou version ultérieure.

Fractionner la fenêtre de tâches

L'intégration d'activités divise la fenêtre de tâches de l'application en deux conteneurs : un conteneur principal et un conteneur secondaire. Ils hébergent les activités lancées à partir de l'activité principale ou d'autres activités qui se trouvent déjà dans les conteneurs.

Les activités sont empilées dans le conteneur secondaire au fur et à mesure de leur lancement. Sur les petits écrans, ce conteneur est également empilé sur le conteneur principal. Dans ce cas, l'empilement des activités et la navigation arrière sont cohérents avec l'ordre des activités déjà intégrées dans l'application.

L'intégration d'activités vous permet d'afficher les activités de différentes manières. Votre application peut lancer deux activités côte à côte et fractionner ainsi la fenêtre de tâches:

Figure 2 : Deux activités côte à côte

De même, une activité qui occupe la totalité de la fenêtre de tâches peut également lancer une nouvelle activité en parallèle et fractionner l'écran :

Figure 3 : L'activité A lance l'activité B à côté

Les activités partageant une fenêtre de tâches et qui sont déjà sur un écran fractionné peuvent lancer d'autres activités de différentes manières :

  • Sur le côté en haut d'une autre activité :

    Figure 4 : L'activité A ouvre l'activité C sur le côté, au-dessus de l'activité B
  • Sur le côté, en déplaçant horizontalement l'écran fractionné et en masquant ainsi l'activité principale précédente:

    Figure 5 : L'activité B lance l'activité C sur le côté et déplace l'écran fractionné horizontalement
  • La nouvelle activité lancée remplace l'activité lancée précédemment dans la même pile d'activités :

    Figure 6. L'activité B lance l'activité C sans indicateur d'intent supplémentaire
  • L'activité lancée s'affiche dans toute la fenêtre dans la même tâche :

    Image 7. L'activité A ou B lance l'activité C, qui remplit la fenêtre de tâches

Navigation vers l'arrière

Différents types d'applications peuvent avoir des règles de navigation arrière différentes lorsque la fenêtre de tâches est fractionnée. Ces règles varient en fonction des dépendances entre les activités ou de la manière dont les utilisateurs déclenchent l'événement "Retour". Voici quelques exemples:

  • Fermeture simultanée des activités: si les activités sont liées et ne peuvent pas s'afficher sans l'autre, la navigation arrière peut être configurée pour les fermer toutes les deux.
  • Fermeture indépendante d'une activité : si les activités sont totalement indépendantes, la navigation arrière au niveau d'une activité n'affecte pas l'état d'une autre activité dans la fenêtre de tâches.

Si vous utilisez un bouton, l'événement "Retour" est envoyé à la dernière activité sélectionnée.

Pour la navigation par gestes:

  • Android 14 (niveau d'API 34) ou version antérieure : l'événement "Retour" est envoyé à l'activité où le geste a eu lieu. Lorsque les utilisateurs balaient l'écran depuis le côté gauche, l'événement "Retour" est envoyé à l'activité dans le volet de gauche de la fenêtre fractionnée. Lorsque les utilisateurs balayent l'écran depuis le côté droit, l'événement "Retour" est envoyé à l'activité dans le volet de droite.

  • Android 15 (niveau d'API 35) ou version ultérieure

    • Lorsque vous travaillez avec plusieurs activités de la même application, le geste arrête l'activité principale, quelle que soit la direction du balayage, offrant ainsi une expérience plus unifiée.

    • Dans les scénarios impliquant deux activités provenant de différentes applications (superposition), l'événement "Retour" est dirigé vers la dernière activité sélectionnée, ce qui correspond au comportement de la navigation par bouton.

Mise en page à plusieurs volets

Jetpack WindowManager vous permet de créer une mise en page à plusieurs volets pour intégrer les activités sur les appareils à grand écran équipés d'Android 12L (niveau d'API 32) ou version ultérieure, et sur certains appareils dotés d'une version antérieure de la plate-forme. Les applications existantes qui sont basées sur plusieurs activités plutôt que sur des fragments ou des mises en page basées sur les vues, comme SlidingPaneLayout, peuvent proposer une meilleure expérience utilisateur sur les grands écrans sans avoir à refactoriser le code source.

Le fractionnement "liste/détail" est un exemple courant. Pour garantir une présentation de qualité, le système lance l'activité "Liste", puis l'application lance immédiatement l'activité "Détail". Le système de transition attend que les deux activités soient dessinées, puis les affiche ensemble. Pour l'utilisateur, ces deux activités sont lancées comme s'il n'en avait qu'une seule.

Figure 8 : Deux activités ont été lancées simultanément dans une mise en page à plusieurs volets

Attributs de fractionnement

Vous pouvez spécifier les proportions des différents conteneurs fractionnés dans la fenêtre de tâches, ainsi que la façon dont ils seront disposés les uns par rapport aux autres.

Pour les règles définies dans un fichier de configuration XML, définissez les attributs suivants :

  • splitRatio : définit les proportions des conteneurs. Cette valeur est un nombre à virgule flottante dans l'intervalle ouvert (0,0, 1,0).
  • splitLayoutDirection: spécifie la mise en page des conteneurs fractionnés. Voici quelques valeurs possibles :
    • ltr : de gauche à droite.
    • rtl : de droite à gauche.
    • locale : la valeur ltr ou rtl est déterminée à partir des paramètres régionaux.

Pour obtenir des exemples, consultez la section Configuration XML.

Pour les règles créées à l'aide des API WindowManager, créez un objet SplitAttributes avec SplitAttributes.Builder et appelez les méthodes de compilateur suivantes:

Pour obtenir des exemples, consultez la section API WindowManager.

Figure 9 : Deux fractionnements d'activités avec une orientation de gauche à droite, mais avec différentes proportions

Espaces réservés

Les activités d'un espace réservé sont des activités secondaires vides qui occupent un espace spécifique dans un fractionnement d'activité. Ils sont destinés à être remplacés par une autre activité comprenant du contenu. Par exemple, l'activité d'un espace réservé peut occuper le conteneur secondaire d'un fractionnement d'activité dans une mise en page Liste/Détail. Dans ce cas, lorsqu'un élément de la liste est sélectionné, une activité contenant les informations détaillées correspondant à cet élément remplace l'espace réservé.

Par défaut, le système n'affiche les espaces réservés que lorsqu'il y a suffisamment d'espace pour un fractionnement d'activité. Les espaces réservés se terminent automatiquement lorsque la largeur ou la hauteur de l'écran devient trop grande ou trop petite pour qu'il soit fractionné. Lorsque l'espace le permet, le système relance l'espace réservé avec un état réinitialisé.

Figure 10. Appareil pliable qui se plie et se déplie. L'activité de l'espace réservé est éliminée, puis relancée lorsque la taille de l'écran change.

Toutefois, l'attribut stickyPlaceholder d'une méthode SplitPlaceholderRule ou setSticky() pour SplitPlaceholder.Builder peut remplacer le comportement par défaut. Lorsque l'attribut ou la méthode spécifie la valeur true, le système affiche l'espace réservé comme étant l'activité la plus élevée dans la fenêtre de tâches lorsque l'écran est redimensionné pour afficher un seul volet à partir d'un écran à deux volets (voir la section Configuration de fractionnement pour consulter un exemple).

Figure 11 : Appareil pliable qui se plie et se déplie. L'activité liée à un espace réservé est persistante.

Changements de taille de la fenêtre

Lorsque la configuration de l'appareil réduit la largeur de la fenêtre de tâches de sorte qu'elle ne soit pas assez grande pour une mise en page à plusieurs volets (par exemple, lorsqu'un appareil pliable à grand écran passe de la taille d'une tablette à celle d'un téléphone ou que la fenêtre de l'application est redimensionnée en mode multifenêtre), les activités autres que les espaces réservés dans le volet secondaire de la fenêtre de tâches sont empilées au-dessus des activités du volet principal.

Les activités d'un espace réservé ne s'affichent que lorsque la largeur de l'écran est suffisante pour le fractionnement. Sur les petits écrans, l'espace réservé est automatiquement fermé. Lorsque la zone d'affichage redevient suffisamment grande, l'espace réservé est recréé. (voir la section Espaces réservés).

L'empilement d'activités est possible, car WindowManager classe les activités dans le volet secondaire au-dessus des activités du volet principal.

Plusieurs activités dans le volet secondaire

L'activité B lance l'activité C sans indicateur d'intent supplémentaire :

Fractionnement contenant les activités A, B et C, avec C empilé sur B.

Les activités sont classées dans cet ordre dans la même tâche :

Pile secondaire contenant l'activité C superposée à B
          La pile secondaire est empilée sur la pile d'activité principale contenant l'activité A.

Par conséquent, dans une fenêtre de tâches de petite taille, l'application se limite à une seule activité et positionne C en haut de la pile:

Fenêtre de petite taille n'affichant que l'activité C.

La navigation arrière dans la fenêtre de petite taille vous permet de parcourir les activités empilées les unes sur les autres.

Si la taille de la fenêtre de tâches est agrandie de sorte à pouvoir accueillir plusieurs volets, les activités s'affichent à nouveau côte à côte.

Fractionnements empilés

L'activité B lance l'activité C sur le côté et déplace l'écran fractionné horizontalement :

Fenêtre de tâches montrant les activités A et B, puis les activités B et C.

Les activités sont classées dans cet ordre dans la même tâche :

Activités A, B et C dans une seule pile. Les activités sont empilées de haut en bas dans l'ordre suivant : C, B, A.

Dans une fenêtre de tâches de petite taille, l'application se limite à une seule activité et positionne C en haut:

Fenêtre de petite taille n'affichant que l'activité C.

Mode portrait fixe

Le paramètre de manifeste android:screenOrientation permet aux applications de contraindre les activités au mode portrait ou paysage. Pour améliorer l'expérience utilisateur sur les grands écrans (tablettes et pliables, par exemple), les fabricants d'appareils (OEM) peuvent ignorer les demandes d'orientation de l'écran et encadrer l'application en mode portrait sur les écrans en mode paysage au format letterbox, ou inversement.

Figure 12 : Activités encadrées au format letterbox : portrait fixe sur l'appareil en mode paysage (à gauche), paysage fixe sur l'appareil en mode portrait (à droite)

De même, lorsque l'intégration d'activités est activée, les OEM peuvent personnaliser les appareils pour encadrer au format letterbox les activités en mode portrait fixe sur les grands écrans (largeur supérieure ou égale à 600 dp) en mode paysage. Lorsqu'une activité de portrait fixe démarre une deuxième activité, l'appareil peut afficher les deux activités côte à côte dans une présentation à deux volets.

Figure 13. L'activité A au format portrait fixe lance l'activité B sur le côté

Ajoutez toujours la propriété android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED au fichier manifeste de votre application pour indiquer aux appareils que cette dernière est compatible avec l'intégration d'activités (voir la section Configuration de fractionnement). Les appareils personnalisés par l'OEM peuvent ensuite déterminer s'il est nécessaire d'utiliser le format letterbox pour les activités en mode portrait.

Configuration de fractionnement

Les règles de fractionnement permettent de configurer les divisions d'activité. Vous définissez les règles de fractionnement dans un fichier de configuration XML ou en effectuant des appels à l'API Jetpack WindowManager.

Dans les deux cas, votre application doit accéder à la bibliothèque WindowManager et informer le système qu'elle a implémenté l'intégration d'activités.

Procédez comme suit :

  1. Ajoutez la dernière dépendance de la bibliothèque WindowManager au fichier build.gradle au niveau du module de votre application, par exemple:

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

    La bibliothèque WindowManager fournit tous les composants requis pour l'intégration d'activités.

  2. Informez le système que votre application a implémenté l'intégration d'activités.

    Ajoutez la propriété android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED à l'élément <application> du fichier manifeste de l'application, puis définissez la valeur sur "true", par exemple:

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

    À partir de la version 1.1.0-alpha06 de WindowManager, les fractionnements de l'intégration d'activités sont désactivés, sauf si la propriété est ajoutée au fichier manifeste et définie sur "true".

    En outre, les fabricants d'appareils utilisent ce paramètre afin d'activer les fonctionnalités personnalisées pour les applications compatibles avec l'intégration d'activités. Par exemple, les appareils peuvent utiliser le format letterbox pour une activité en mode portrait sur les affichages en mode paysage. L'activité sera ainsi orientée de sorte à passer à une mise en page à deux volets lorsqu'une deuxième activité commencera (voir Mode portrait fixe).

Configuration XML

Pour créer une implémentation XML de l'intégration d'activités, procédez comme suit:

  1. Créez un fichier de ressources XML qui :

    • définit les activités qui partagent un fractionnement ;
    • Configure les options de fractionnement.
    • Crée un espace réservé pour le conteneur secondaire du fractionnement lorsque le contenu n'est pas disponible.
    • Spécifie les activités qui ne doivent jamais faire partie d'un fractionnement.

    Exemple :

    <!-- 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. Créez un initialiseur.

    Le composant RuleController de WindowManager analyse le fichier de configuration XML et met les règles à disposition du système. Une bibliothèque Jetpack Startup Initializer met le fichier XML à disposition de RuleController au démarrage de l'application, afin que les règles s'appliquent au démarrage d'une activité.

    Pour créer un initialiseur, procédez comme suit :

    1. Ajoutez la dernière dépendance de la bibliothèque Jetpack Startup à votre fichier build.gradle au niveau du module, par exemple:

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

    2. Créez une classe qui implémente l'interface Initializer.

      L'initialiseur met les règles de fractionnement à disposition de RuleController en transmettant l'ID du fichier de configuration XML (main_split_config.xml) à la méthode 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. Créez un fournisseur de contenu pour les définitions de règles.

    Ajoutez androidx.startup.InitializationProvider au fichier manifeste de votre application en tant que <provider>. Incluez une référence à l'implémentation de l'initialiseur 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 détecte et initialise SplitInitializer avant d'appeler la méthode onCreate() de l'application. Par conséquent, les règles de fractionnement sont appliquées lorsque l'activité principale de l'application commence.

API WindowManager

Vous pouvez implémenter l'intégration d'une activité de manière programmatique avec quelques appels d'API. Effectuez les appels dans la méthode onCreate() d'une sous-classe de Application pour vous assurer que les règles sont en vigueur avant le lancement des activités.

Pour créer un fractionnement des activités de manière programmatique, procédez comme suit :

  1. Créez une règle de fractionnement :

    1. Créez un SplitPairFilter qui identifie les activités qui partagent le fractionnement:

      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. Ajoutez le filtre à un ensemble de filtres :

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
    3. Créez des attributs de mise en page pour le fractionnement :

      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 crée un objet contenant des attributs de mise en page:

      • setSplitType(): définit la manière dont la zone d'affichage disponible est allouée à chaque conteneur d'activité. Le type de fractionnement des proportions spécifie la proportion de la zone d'affichage disponible allouée au conteneur principal. Le conteneur secondaire occupe le reste de la zone d'affichage.
      • setLayoutDirection(): spécifie la disposition des conteneurs d'activités les uns par rapport aux autres (le conteneur principal en premier).
    4. Créez une 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 crée et configure la règle :

      • filterSet: contient des filtres de paire de fractionnement qui déterminent quand appliquer la règle en identifiant les activités qui partagent un fractionnement.
      • setDefaultSplitAttributes(): applique des attributs de mise en page à la règle.
      • setMinWidthDp(): définit la largeur d'affichage minimale (en pixels indépendants de la densité, dp) pour permettre le fractionnement.
      • setMinSmallestWidthDp(): définit la valeur minimale (en dp) que la plus petite des deux dimensions d'affichage doit avoir pour permettre un fractionnement, quelle que soit l'orientation de l'appareil.
      • setMaxAspectRatioInPortrait(): définit le format maximal d'affichage (height:width) en mode portrait pour lequel les fractionnements d'activité seront affichés. Si le format d'un écran en mode portrait dépasse le format maximal, les écrans fractionnés sont désactivés, quelle que soit la largeur de l'écran. Remarque:La valeur par défaut est 1, 4.Les activités occupent donc toute la fenêtre de tâches en mode portrait sur la plupart des tablettes. Voir également SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT et setMaxAspectRatioInLandscape(). La valeur par défaut pour le mode paysage est ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary(): définit l'impact de toutes les activités effectuées dans le conteneur secondaire sur les activités du conteneur principal. NEVER indique que le système ne doit pas terminer les activités principales lorsque toutes les activités du conteneur secondaire sont finalisées (voir la section Arrêter les activités).
      • setFinishSecondaryWithPrimary(): définit l'impact de toutes les activités effectuées dans le conteneur principal sur les activités du conteneur secondaire. ALWAYS indique que le système doit toujours terminer les activités du conteneur secondaire lorsque toutes les activités du conteneur principal sont finalisées (voir la section Arrêter les activités).
      • setClearTop(): indique si toutes les activités dans le conteneur secondaire sont arrêtées lorsqu'une nouvelle activité est lancée dans le conteneur. La valeur false indique que les nouvelles activités sont empilées sur les activités déjà présentes dans le conteneur secondaire.
    5. Obtenez l'instance du singleton du RuleController WindowManager, puis ajoutez la règle:

      Kotlin

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

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. Créez un espace réservé pour le conteneur secondaire lorsque le contenu n'est pas disponible:

    1. Créez un ActivityFilter qui identifie l'activité avec laquelle l'espace réservé partage un fractionnement de fenêtre de tâches:

      Kotlin

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

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. Ajoutez le filtre à un ensemble de filtres :

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
    3. Créez 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 crée et configure la règle :

      • placeholderActivityFilterSet: contient des filtres d'activité qui déterminent quand appliquer la règle en identifiant les activités auxquelles l'activité d'espace réservé est associée.
      • Intent : spécifie le lancement de l'activité d'espace réservé.
      • setDefaultSplitAttributes() : applique des attributs de mise en page à la règle.
      • setMinWidthDp() : définit la largeur d'affichage minimale (en pixels indépendants de la densité, dp) pour permettre le fractionnement.
      • setMinSmallestWidthDp() : définit la valeur minimale (en dp) que la plus petite des deux dimensions d'affichage doit avoir pour permettre un fractionnement, quelle que soit l'orientation de l'appareil.
      • setMaxAspectRatioInPortrait() : définit le format maximal d'affichage (height:width) en mode portrait pour lequel les fractionnements d'activité seront affichés. Remarque:La valeur par défaut est 1, 4.Les activités occupent donc toute la fenêtre de tâches en mode portrait sur la plupart des tablettes. Voir également SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT et setMaxAspectRatioInLandscape(). La valeur par défaut pour le mode paysage est ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder() : définit l'impact de la finalisation de l'activité liée à un espace réservé sur les activités du conteneur principal. "ALWAYS" (TOUJOURS) indique que le système doit toujours terminer les activités dans le conteneur principal lorsque l'espace réservé est finalisé (voir la section Arrêter les activités).
      • setSticky(): détermine si l'activité d'espace réservé s'affiche en haut de la pile d'activités sur les petits écrans une fois qu'il apparaît pour la première fois dans un écran fractionné avec une largeur minimale suffisante.
    4. Ajoutez la règle à la fenêtre RuleController de WindowManager :

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. Spécifiez les activités qui ne doivent jamais faire partie d'un fractionnement :

    1. Créez un ActivityFilter qui identifie une activité qui doit toujours occuper la totalité de la zone d'affichage des tâches:

      Kotlin

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

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. Ajoutez le filtre à un ensemble de filtres :

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. Créez une ActivityRule :

      Kotlin

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

      Java

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

      ActivityRule.Builder crée et configure la règle :

      • expandedActivityFilterSet: contient les filtres d'activité qui déterminent quand appliquer la règle en identifiant les activités que vous souhaitez exclure des écrans fractionnés.
      • setAlwaysExpand(): indique si l'activité doit remplir toute la fenêtre de tâches.
    4. Ajoutez la règle à la fenêtre RuleController de WindowManager :

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

Intégration entre les applications

Sur Android 13 (niveau d'API 33) ou version ultérieure, les applications peuvent intégrer des activités provenant d'autres applications. L'intégration d'activités entre les applications, également appelée UID, permet de rassembler visuellement les activités de plusieurs applications Android. Le système affiche à l'écran une activité de l'application hôte et une activité intégrée provenant d'une autre application côte-à-côte ou en haut et en bas, comme pour l'intégration d'activités dans une application unique.

Par exemple, l'application Paramètres peut intégrer l'activité de sélecteur de fond d'écran à partir de l'application WallpaperPicker:

Figure 14 : Application Paramètres (menu sur la gauche) avec sélecteur de fond d'écran en tant qu'activité intégrée (à droite)

Modèle de confiance

Les processus hôtes qui intègrent des activités provenant d'autres applications sont en mesure de redéfinir la présentation des activités intégrées, y compris la taille, la position, le recadrage et la transparence. Des hôtes malveillants peuvent exploiter cette fonctionnalité pour induire les utilisateurs en erreur et créer des attaques de détournement de clic ou d'autres tentatives de redressement de l'UI.

Pour éviter l'usage abusif de l'intégration d'activités entre les applications, Android demande aux applications d'autoriser l'intégration de leurs activités. Elles peuvent désigner des hôtes approuvés ou non.

Hôtes approuvés

Pour permettre à d'autres applications d'intégrer et de contrôler totalement la présentation des activités à partir de votre application, spécifiez le certificat SHA-256 de l'application hôte dans l'attribut android:knownActivityEmbeddingCerts des éléments <activity> ou <application> de son fichier manifeste.

Définissez la valeur de android:knownActivityEmbeddingCerts sous forme de chaîne :

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

ou, pour spécifier plusieurs certificats, un tableau de chaînes :

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

qui fait référence à une ressource semblable à celle-ci :

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

Les propriétaires d'applications peuvent obtenir un condensé de certificat SHA en exécutant la tâche signingReport Gradle. Le condensé du certificat correspond à l'empreinte SHA-256, sans les deux-points de séparation. Pour en savoir plus, consultez Générer un rapport de signature et Authentifier votre client.

Hôtes non approuvés

Pour autoriser n'importe quelle application à intégrer les activités de votre application et à contrôler leur présentation, spécifiez l'attribut android:allowUntrustedActivityEmbedding dans les éléments <activity> ou <application> du fichier manifeste de l'application, par exemple:

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

La valeur par défaut de cet attribut est "false", ce qui empêche l'intégration d'activités entre les applications.

Authentification personnalisée

Pour atténuer les risques liés à l'intégration d'activités non approuvées, créez un mécanisme d'authentification personnalisé qui valide l'identité de l'hôte. Si vous connaissez les certificats hôtes, utilisez la bibliothèque androidx.security.app.authenticator pour vous authentifier. Si l'hôte s'authentifie après l'intégration de votre activité, vous pouvez afficher le contenu réel. Sinon, vous pouvez informer l'utilisateur que l'action n'a pas été autorisée et bloquer le contenu.

Utilisez la méthode ActivityEmbeddingController#isActivityEmbedded() de la bibliothèque Jetpack WindowManager pour vérifier si un hôte intègre votre activité, par exemple:

Kotlin

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

Java

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

Restriction liée à la taille minimale

Le système Android applique la hauteur et la largeur minimales spécifiées dans l'élément <layout> du fichier manifeste de l'application aux activités intégrées. Si une application ne spécifie pas de hauteur ni de largeur minimales, les valeurs par défaut du système s'appliquent (sw220dp).

Si l'hôte tente de redimensionner le conteneur intégré en dessous de la valeur minimale, le conteneur intégré se développe pour occuper toutes les limites de la tâche.

<activity-alias>

Pour que l'intégration d'activités approuvées ou non fonctionne avec l'élément <activity-alias>, android:knownActivityEmbeddingCerts ou android:allowUntrustedActivityEmbedding doit être appliqué à l'activité cible plutôt qu'à l'alias. La règle qui confirme la sécurité sur le serveur système est basée sur les indicateurs définis sur la cible, et non sur l'alias.

Application hôte

Les applications hôtes implémentent l'intégration d'activités entre les applications de la même manière que l'intégration d'activités dans une seule application. Les objets SplitPairRule et SplitPairFilter ou ActivityRule et ActivityFilter spécifient l'intégration activités et le fractionnement de la fenêtre de tâches. Les règles de fractionnement sont définies de manière statique au format XML ou au moment de l'exécution à l'aide d'appels à l'API Jetpack WindowManager.

Si une application hôte tente d'intégrer une activité pour laquelle l'intégration entre les applications n'a pas été activée, l'activité occupe l'intégralité des limites de la tâche. Par conséquent, les applications hôtes doivent savoir si les activités cibles autorisent l'intégration entre les applications ou non.

Si une activité intégrée lance une nouvelle activité dans la même tâche et que la nouvelle activité ne permet pas l'intégration entre les applications, elle occupe la totalité des limites de la tâche au lieu d'être superposée à l'activité dans le conteneur intégré.

Une application hôte peut intégrer ses propres activités sans restriction, à condition qu'elles soient lancées dans la même tâche.

Exemples de fractionnement

Fractionnement de la fenêtre entière

Figure 15 : L'activité A lance l'activité B à côté

Aucune refactorisation n'est nécessaire. Vous pouvez définir la configuration de fractionnement en mode statique ou au moment de l'exécution, puis appeler Context#startActivity() sans aucun paramètre supplémentaire.

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

Fractionnement par défaut

Lorsque la page de destination d'une application est conçue pour être fractionnée en deux conteneurs sur les grands écrans, l'expérience utilisateur est optimale lorsque les deux activités sont créées et présentées simultanément. Toutefois, le contenu destiné au conteneur secondaire du fractionnement n'est pas toujours disponible tant que l'utilisateur n'a pas interagi avec l'activité du conteneur principal (tant qu'il n'a pas sélectionné un élément dans un menu de navigation, par exemple). Une activité d'espace réservé peut combler le vide jusqu'à ce que le contenu puisse être affiché dans le conteneur secondaire du fractionnement (voir la section Espaces réservés).

Figure 16 : Fractionnement créé en ouvrant deux activités simultanément. L'une des activités correspond à un espace réservé.

Pour créer un fractionnement avec un espace réservé, créez un espace réservé et associez-le à l'activité principale:

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

Lorsqu'une application reçoit un intent, l'activité cible peut être affichée en tant que partie secondaire d'un fractionnement d'activité (requête d'affichage d'un écran détaillé contenant des informations sur l'élément d'une liste, par exemple). Sur les petits écrans, le détail s'affiche dans la fenêtre de tâches entière. Sur les grands écrans, il s'affiche à côté de la liste.

Figure 17 : Activité "Détail" affichée seule sur un petit écran grâce à un lien profond et affichée avec l'activité "Liste" sur un grand écran

La requête de lancement doit être acheminée vers l'activité principale, et l'activité cible destinée aux détails doit être lancée dans un écran fractionné. Le système choisit automatiquement la présentation appropriée (empilée ou côte à côte) en fonction de la largeur d'affichage disponible.

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 destination du lien profond peut être la seule activité disponible pour l'utilisateur dans la pile de navigation arrière, auquel cas il peut être utile de ne pas fermer l'activité "Détail" pour n'afficher que l'activité principale:

Grand écran avec l&#39;activité &quot;Liste&quot; et l&#39;activité &quot;Détail&quot; côte à côte.
          Navigation arrière qui ne parvient pas à ignorer l&#39;activité &quot;Détail&quot; et à laisser l&#39;activité &quot;Liste&quot; à l&#39;écran.

Petit écran n&#39;affichant que l&#39;activité &quot;Détail&quot;. Navigation arrière qui ne parvient pas à ignorer l&#39;activité &quot;Détail&quot; et à afficher l&#39;activité &quot;Liste&quot; à l&#39;écran.

À la place, vous pouvez arrêter les deux activités simultanément avec l'attribut finishPrimaryWithSecondary :

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

Consultez la section Attributs de configuration.

Plusieurs activités dans des conteneurs fractionnés

L'empilement de plusieurs activités dans un conteneur fractionné permet aux utilisateurs d'accéder à un contenu profond. Par exemple, avec un fractionnement Liste/Détail, l'utilisateur doit parfois accéder à une section de sous-détails tout en conservant l'activité principale:

Figure 18 : Activité ouverte dans le volet secondaire complet de la fenêtre de tâches

Kotlin

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

Java

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

L'activité du sous-détail est placée au-dessus de l'activité du détail et la dissimule :

L'utilisateur peut ensuite revenir au niveau de détail précédent via la navigation arrière dans la pile :

Figure 19 : Activité supprimée du haut de la pile

Les activités sont empilées par défaut lorsqu'elles sont lancées à partir d'une activité qui se trouve dans le même conteneur secondaire. Les activités lancées à partir du conteneur principal dans un fractionnement actif se retrouvent également dans le conteneur secondaire, au-dessus de la pile d'activités.

Activités dans une nouvelle tâche

Lorsque des activités d'une fenêtre de tâches fractionnée déclenchent d'autres activités dans une nouvelle tâche, cette dernière est distincte de la tâche qui inclut le fractionnement et s'affiche en plein écran. L'écran "Recents" (Éléments récents) affiche deux tâches : la tâche sur l'écran fractionné et la nouvelle tâche.

Figure 20 : Lancement de l'activité C dans une nouvelle tâche à partir de l'activité B

Remplacement d'activité

Les activités peuvent être remplacées dans la pile du conteneur secondaire (lorsque l'activité principale est utilisée pour la navigation de premier niveau et que l'activité secondaire est une destination sélectionnée, par exemple). Chaque sélection dans la navigation de premier niveau devrait lancer une nouvelle activité dans le conteneur secondaire et supprimer l'activité ou les activités qui s'y trouvaient précédemment.

Figure 21 : L'activité de navigation de premier niveau dans le volet principal remplace les activités de destination dans le volet secondaire

Si l'application ne termine pas l'activité du conteneur secondaire lorsque la sélection change dans la navigation, la navigation arrière peut être source de confusion lorsque le fractionnement est réduit (lorsque l'appareil est plié). Par exemple, si vous avez un menu dans le volet principal et que les écrans A et B sont empilés dans le volet secondaire, lorsque l'utilisateur plie le téléphone, B se trouvera au-dessus de l'écran A, qui se trouvera lui-même au-dessus du menu. Lorsque l'utilisateur effectuera un retour arrière à partir de B, l'écran A apparaîtra au lieu du menu.

Dans ce cas, l'écran A doit être supprimé de la pile "Retour".

Par défaut, le lancement sur le côté dans un nouveau conteneur au-dessus d'un écran déjà fractionné place les nouveaux conteneurs secondaires au-dessus et conserve les anciens dans la pile "Retour". Vous pouvez configurer les fractionnements pour que les conteneurs secondaires précédents soient effacés avec clearTop et lancer normalement les nouvelles activités.

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

Vous pouvez également utiliser la même activité secondaire et, à partir de l'activité principale (menu), envoyer de nouveaux intents qui renvoient à la même instance, mais déclenchent une mise à jour de l'état ou de l'interface utilisateur dans le conteneur secondaire.

Fonctionnement de plusieurs écrans fractionnés

Les applications peuvent fournir une navigation profonde à plusieurs niveaux en lançant des activités supplémentaires sur le côté.

Lorsqu'une activité dans un conteneur secondaire lance une nouvelle activité sur le côté, un écran fractionné est créé au-dessus de l'écran fractionné existant.

Figure 22 : L'activité B lance l'activité C sur le côté

La pile "Retour" contient toutes les activités qui ont été ouvertes afin que les utilisateurs puissent accéder à l'écran fractionné A/B une fois qu'ils auront fermé l'écran C.

Activités A, B et C dans une pile. Les activités sont empilées de haut en bas dans l&#39;ordre suivant : C, B, A.

Pour créer un autre écran fractionné, lancez la nouvelle activité sur le côté du conteneur secondaire existant. Déclarez les configurations correspondant aux écrans fractionnés A/B et B/C, puis lancez l'activité C normalement à partir de 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));
    }
}

Réagir aux changements d'état des écrans fractionnés

Différentes activités d'application peuvent comporter des éléments d'interface utilisateur ayant la même fonction (commande qui ouvre une fenêtre contenant les paramètres du compte, par exemple).

Figure 23 : Activités distinctes avec des éléments d'interface utilisateur identiques du point de vue fonctionnel

Si deux activités qui partagent un élément d'interface utilisateur commun sont fractionnées, il est redondant et parfois déroutant d'afficher cet élément dans les deux activités.

Figure 24 : Éléments d'interface utilisateur en double dans les écrans fractionnés d'une activité

Pour déterminer quand les activités sont dans un écran fractionné, consultez le flux SplitController.splitInfoList ou enregistrez un écouteur avec SplitControllerCallbackAdapter afin de suivre les changements d'état des écrans fractionnés. Ajustez ensuite l'interface utilisateur en conséquence:

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

Les coroutines peuvent être lancées à n'importe quel état du cycle de vie, mais elles sont généralement lancées à l'état STARTED pour préserver les ressources (voir la section Utiliser des coroutines Kotlin avec des composants tenant compte du cycle de vie pour en savoir plus).

Les rappels peuvent être effectués à n'importe quel état du cycle de vie, y compris lorsqu'une activité est arrêtée. L'enregistrement des écouteurs doit généralement être activé dans onStart() et être annulé dans onStop().

Fenêtre modale plein écran

Certaines activités empêchent les utilisateurs d'interagir avec l'application tant qu'une action spécifique n'a pas été effectuée (par exemple, un écran d'authentification, un écran de confirmation d'un règlement ou un message d'erreur). Les activités modales ne doivent pas s'afficher dans des écrans fractionnés.

Pour forcer une activité à toujours remplir la fenêtre de tâches, utilisez la configuration d'expansion :

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

Arrêter les activités

Les utilisateurs peuvent arrêter les activités de chaque côté de l'écran fractionné en balayant l'écran depuis le bord:

Figure 25 : Geste de balayage de l'écran qui met fin à l'activité B
Figure 26 : Geste de balayage de l'écran qui met fin à l'activité A

Si l'appareil est configuré pour utiliser le bouton "Retour" au lieu de la navigation par gestes, l'entrée est envoyée à l'activité sélectionnée, à savoir celle sur laquelle l'utilisateur a appuyé en dernier ou celle qu'il a lancée en dernier.

L'impact de la finalisation de toutes les activités d'un conteneur sur le conteneur opposé dépend de la configuration du fractionnement.

Attributs de configuration

Vous pouvez spécifier des attributs de règle pour la paire fractionnée afin de configurer l'impact de toutes les activités effectuées d'un côté sur les activités qui se trouvent de l'autre côté du fractionnement. Les attributs sont les suivants :

  • window:finishPrimaryWithSecondary : impact de toutes les activités effectuées dans le conteneur secondaire sur les activités du conteneur principal
  • window:finishSecondaryWithPrimary : impact de toutes les activités effectuées dans le conteneur principal sur les activités du conteneur secondaire

Les valeurs possibles des attributs sont les suivantes :

  • always : toujours terminer les activités dans le conteneur associé
  • never : ne jamais terminer les activités dans le conteneur associé
  • adjacent : terminer les activités dans le conteneur associé lorsque les deux conteneurs sont affichés l'un à côté de l'autre, mais pas lorsqu'ils sont empilés l'un au-dessus de l'autre

Exemple :

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

Configuration par défaut

Lorsque toutes les activités d'un conteneur sont finalisées, le conteneur restant occupe la totalité de la fenêtre:

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

Écran fractionné contenant les activités A et B. L&#39;activité A est arrêtée. En conséquence, l&#39;activité B occupe la fenêtre entière.

Écran fractionné contenant les activités A et B. L&#39;activité B est arrêtée. En conséquence, l&#39;activité A occupe la fenêtre entière.

Mettre fin aux activités simultanément

Pour mettre fin automatiquement aux activités du conteneur principal lorsque toutes les activités du conteneur secondaire sont finalisées:

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

Écran fractionné contenant les activités A et B. L&#39;activité B est arrêtée, ce qui met également fin à l&#39;activité A, laissant la fenêtre de tâches vide.

Écran fractionné contenant les activités A et B. L&#39;activité A est arrêtée. L&#39;activité B apparaît seule dans la fenêtre de tâches.

Pour mettre fin automatiquement aux activités du conteneur secondaire lorsque toutes les activités du conteneur principal sont finalisées:

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

Écran fractionné contenant les activités A et B. L&#39;activité A est arrêtée, ce qui met également fin à l&#39;activité B, laissant la fenêtre de tâches vide.

Écran fractionné contenant les activités A et B. L&#39;activité B est arrêtée. L&#39;activité A apparaît seule dans la fenêtre de tâches.

Pour mettre fin aux activités simultanément lorsque toutes les activités du conteneur principal ou secondaire sont finalisées:

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

Écran fractionné contenant les activités A et B. L&#39;activité A est arrêtée, ce qui met également fin à l&#39;activité B, laissant la fenêtre de tâches vide.

Écran fractionné contenant les activités A et B. L&#39;activité B est arrêtée, ce qui met également fin à l&#39;activité A, laissant la fenêtre de tâches vide.

Mettre fin à plusieurs activités dans les conteneurs

Si plusieurs activités sont empilées dans un conteneur fractionné, l'arrêt d'une activité au bas de la pile ne met pas automatiquement fin aux activités en haut de la pile.

Par exemple, si deux activités se trouvent dans le conteneur secondaire, C au-dessus de B :

La pile secondaire contenant l&#39;activité C empilée sur B, est empilée sur la pile principale contenant l&#39;activité A.

Et si la configuration du fractionnement est définie par la configuration des activités A et B:

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

L'arrêt de l'activité située en haut de la pile conserve le fractionnement.

Écran fractionné avec l&#39;activité A dans le conteneur principal et les activités B et C dans le conteneur secondaire, avec l&#39;activité C empilée sur B. L&#39;activité C est arrêtée, ce qui laisse A et B dans l&#39;écran fractionné.

L'arrêt de l'activité inférieure (racine) du conteneur secondaire n'entraîne pas la suppression des activités qui se trouvent au-dessus, et conserve donc aussi le fractionnement.

Écran fractionné avec l&#39;activité A dans le conteneur principal et les activités B et C dans le conteneur secondaire, avec l&#39;activité C empilée sur B. L&#39;activité B est arrêtée, ce qui laisse A et C dans l&#39;écran fractionné.

Toute règle supplémentaire permettant de mettre fin aux activités simultanément, telle que l'arrêt de l'activité secondaire en même temps que l'activité principale, est également exécutée:

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

Écran fractionné avec l&#39;activité A dans le conteneur principal et les activités B et C dans le conteneur secondaire, avec l&#39;activité C empilée sur B. L&#39;activité A est arrêtée, ce qui met également fin aux activités B et C.

Et lorsque le fractionnement est configuré pour mettre fin à l'activité principale et à l'activité secondaire simultanément :

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

Écran fractionné avec l&#39;activité A dans le conteneur principal et les activités B et C dans le conteneur secondaire, avec l&#39;activité C empilée sur B. L&#39;activité C est arrêtée, ce qui laisse A et B dans l&#39;écran fractionné.

Écran fractionné avec l&#39;activité A dans le conteneur principal et les activités B et C dans le conteneur secondaire, avec l&#39;activité C empilée sur B. L&#39;activité B est arrêtée, ce qui laisse A et C dans l&#39;écran fractionné.

Écran fractionné avec l&#39;activité A dans le conteneur principal et les activités B et C dans le conteneur secondaire, avec l&#39;activité C empilée sur B. L&#39;activité A est arrêtée, ce qui met également fin aux activités B et C.

Modifier les propriétés de fractionnement au moment de l'exécution

Il n'est pas possible de modifier les propriétés d'un fractionnement actif et visible. La modification des règles de fractionnement s'applique au lancement d'activités supplémentaires et aux nouveaux conteneurs, pas aux fractionnements actifs existants.

Pour modifier les propriétés des fractionnements actifs, arrêtez les activités secondaires dans l'écran fractionné, puis lancez-les de nouveau sur le côté avec une nouvelle configuration.

Propriétés de fractionnement dynamique

Android 15 (niveau d'API 35) ou version ultérieure, compatible avec Jetpack WindowManager 1.4 ou version ultérieure, offre des fonctionnalités dynamiques qui permettent de configurer les fractionnements d'intégration d'activités, y compris:

  • Élargissement des volets:un séparateur interactif et déplaçable permet aux utilisateurs de redimensionner les volets dans une présentation fractionnée.
  • Épinglage de la pile d'activités:les utilisateurs peuvent épingler le contenu dans un conteneur et isoler la navigation dans ce conteneur de celle dans l'autre.
  • Atténuation de la luminosité des boîtes de dialogue en plein écran:lorsque vous affichez une boîte de dialogue, les applications peuvent spécifier si elles doivent atténuer la luminosité de l'intégralité de la fenêtre de tâche ou uniquement du conteneur qui a ouvert la boîte de dialogue.

Expansion des volets

L'expansion des volets permet aux utilisateurs d'ajuster l'espace d'écran alloué aux deux activités dans une mise en page à double volet.

Pour personnaliser l'apparence du séparateur de fenêtre et définir la plage de glissement du séparateur, procédez comme suit:

  1. Créer une instance de DividerAttributes

  2. Personnalisez les attributs du séparateur:

    • color:couleur du séparateur de volets déplaçable.

    • widthDp:largeur du séparateur de volets déplaçable. Définissez la valeur sur WIDTH_SYSTEM_DEFAULT pour laisser le système déterminer la largeur du séparateur.

    • Plage de glissement:pourcentage minimal de l'écran que chaque volet peut occuper. Peut varier de 0,33 à 0,66. Définissez la valeur sur DRAG_RANGE_SYSTEM_DEFAULT pour laisser le système déterminer la plage de glissement.

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

Épinglage de la pile d'activités

L'épinglage de la pile d'activités permet aux utilisateurs d'épingler l'une des fenêtres fractionnées afin que l'activité reste telle quelle pendant que les utilisateurs naviguent dans l'autre fenêtre. L'épinglage de la pile d'activités offre une expérience multitâche améliorée.

Pour activer l'épinglage de la pile d'activités dans votre application, procédez comme suit:

  1. Ajoutez un bouton au fichier de mise en page de l'activité que vous souhaitez épingler, par exemple l'activité "Détail" d'une mise en page Liste/Détail:

    <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. Dans la méthode onCreate() de l'activité, définissez un écouteur onclick sur le bouton:

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

Diminution de la luminosité des boîtes de dialogue en plein écran

Les activités atténuent généralement leur écran pour attirer l'attention sur une boîte de dialogue. Dans l'intégration d'activités, les deux volets de l'écran à deux volets doivent s'assombrir, et non seulement le volet contenant l'activité qui a ouvert la boîte de dialogue, pour une expérience d'UI unifiée.

Avec WindowManager 1.4 ou version ultérieure, l'intégralité de la fenêtre de l'application est atténuée par défaut lorsqu'une boîte de dialogue s'ouvre (voir EmbeddingConfiguration.DimAreaBehavior.ON_TASK).

Pour diminuer uniquement la luminosité du conteneur de l'activité qui a ouvert la boîte de dialogue, utilisez EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.

Extraire une activité d'un écran fractionné pour l'afficher dans la fenêtre complète

Créez une configuration qui affichera l'activité secondaire dans une fenêtre complète, puis relancez cette activité avec un intent qui résout la même instance.

Vérifier la prise en charge du fractionnement au moment de l'exécution

L'intégration d'activités est compatible avec Android 12L (niveau d'API 32) ou version ultérieure. Elle est également disponible sur certains appareils exécutant des versions de plate-forme antérieures. Pour vérifier la disponibilité de cette fonctionnalité au moment de l'exécution, utilisez la propriété SplitController.splitSupportStatus ou la méthode 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.
}

Si les fractionnements ne sont pas pris en charge, les activités sont lancées au-dessus de la pile d'activités (en suivant le modèle d'intégration hors activité).

Empêcher le remplacement du système

Les fabricants d'appareils Android (fabricants d'équipement d'origine, ou OEM) peuvent implémenter l'intégration d'activités en tant que fonction du système de l'appareil. Le système spécifie des règles de fractionnement pour les applications à plusieurs activités, ignorant ainsi leur comportement de fenêtrage. Le remplacement du système force les applications à plusieurs activités à utiliser un mode d'intégration d'activités défini par le système.

L'intégration des activités du système peut améliorer la présentation de l'appli au moyen de mises en page à plusieurs volets, telle que list-detail, sans modifier l'appli. Toutefois, ce type d'intégration peut également donner lieu à des mises en page d'application incorrectes, des bugs ou des conflits avec l'intégration des activités implémentée par l'appli.

Votre application peut empêcher ou autoriser l'intégration des activités système en définissant une propriété dans son fichier manifeste, par exemple:

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

Le nom de la propriété est défini dans l'objet WindowProperties de Jetpack WindowManager. Définissez la valeur sur false si votre application implémente l'intégration d'activités ou si vous souhaitez empêcher le système d'appliquer ses règles d'intégration d'activités à votre application. Définissez la valeur sur true pour autoriser le système à appliquer son intégration d'activités à votre application.

Limites, restrictions et mises en garde

  • Seule l'application hôte de la tâche, identifiée comme propriétaire de l'activité racine, peut y organiser et y intégrer d'autres activités. Si les activités compatibles avec l'intégration et le fractionnement s'exécutent dans une tâche appartenant à une autre application, ces opérations ne fonctionneront pas.
  • Les activités ne peuvent être organisées que dans une seule tâche. Le lancement d'une activité dans une nouvelle tâche la place toujours dans une nouvelle fenêtre développée en dehors des écrans fractionnés existants.
  • Seules les activités d'un même processus peuvent être organisées et fractionnées. Le rappel SplitInfo ne signale que les activités qui appartiennent au même processus, car il n'existe aucun moyen d'identifier les activités associées à d'autres processus.
  • Chaque règle d'activité unique ou par paire s'applique uniquement aux lancements d'activités qui se produisent après l'enregistrement de la règle. Il n'existe actuellement aucun moyen de mettre à jour les fractionnements actifs ni leurs propriétés visuelles.
  • La configuration du filtre de paire de fractionnement doit correspondre aux intents utilisés lors du lancement complet des activités. La mise en correspondance intervient au moment où une nouvelle activité est lancée à partir du processus d'application. Il est donc possible qu'elle ne connaisse pas les noms de composants résolus ultérieurement dans le processus système lors de l'utilisation d'intents implicites. Si le nom d'un composant n'est pas connu au moment du lancement, vous pouvez utiliser un caractère générique ("*/*") à la place, et un filtrage basé sur l'action d'intent.
  • Il n'existe actuellement aucun moyen de déplacer des activités entre des conteneurs, ni de les ajouter à un écran fractionné ou de les en sortir après leur création. Les écrans fractionnés ne sont créés par la bibliothèque WindowManager que lorsque de nouvelles activités avec des règles associées sont lancées. Les fractionnements sont entièrement éliminés à l'arrêt de la dernière activité dans un écran fractionné.
  • Les activités peuvent être redémarrées lorsque la configuration change. Dès lors, lorsqu'un fractionnement est créé ou supprimé et que les limites de l'activité changent, celle-ci peut subir une destruction complète, puis être créée à nouveau. Par conséquent, les développeurs d'applications doivent prêter attention à des comportements spécifiques tels que le lancement de nouvelles activités à partir de rappels de cycle de vie.
  • Les appareils doivent inclure l'interface des extensions de fenêtre pour permettre l'intégration d'activités. Presque tous les appareils à grand écran équipés d'Android 12L (niveau d'API 32) ou version ultérieure incluent cette interface. Toutefois, ce n'est pas le cas de certains appareils à grand écran qui ne permettent pas l'exécution de plusieurs activités. Si un appareil à grand écran n'est pas compatible avec le mode multifenêtre, il est possible qu'il ne permette pas l'intégration d'activités.

Ressources supplémentaires