Intégration d'activités avancée

1. Introduction

L'intégration d'activités, introduite dans Android 12L (niveau d'API 32), permet aux applications basées sur des activités d'afficher plusieurs activités simultanément sur les grands écrans en créant une mise en page à deux volets (Liste/Détail, par exemple).

L'atelier de programmation Créer une mise en page de type "Liste/Détail" avec l'intégration d'activités et Material Design vous a montré comment utiliser des appels d'API XML ou Jetpack WindowManager pour créer une mise en page de type "Liste/Détail".

Cet atelier de programmation vous présente quelques nouvelles fonctionnalités d'intégration d'activités, qui améliorent davantage l'expérience dans les applications sur les appareils à grand écran. Les fonctionnalités incluent l'expansion des volets, l'épinglage d'activités et la diminution de la luminosité des boîtes de dialogue en plein écran.

Prérequis

Points abordés

Vous découvrirez comment effectuer les actions suivantes :

  • Activer l'expansion des volets
  • Implémenter l'épinglage d'activités avec l'une des fenêtres fractionnées
  • Diminuer la luminosité des boîtes de dialogue en plein écran

Ce dont vous avez besoin

  • Version récente d'Android Studio
  • Téléphone ou émulateur Android avec Android 15
  • Grande tablette ou émulateur Android avec une largeur minimale supérieure à 600 dp

2. Configuration

Obtenir l'application exemple

Étape 1 : Cloner le dépôt

Clonez le dépôt Git pour les ateliers de programmation spécifiques aux grands écrans :

git clone https://github.com/android/large-screen-codelabs

Vous pouvez aussi télécharger et décompresser le fichier ZIP correspondant aux ateliers de programmation pour les grands écrans :

Télécharger le code source

Étape 2 : Inspecter les fichiers sources de l'atelier de programmation

Accédez au dossier activity-embedding-advanced :

Étape 3 : Ouvrir le projet de l'atelier de programmation

Dans Android Studio, ouvrez le projet Kotlin ou Java.

Liste des fichiers du dossier d'intégration d'activités dans le dépôt et le fichier ZIP.

Le dossier activity-embedding-advanced du dépôt et du fichier ZIP contient deux projets Android Studio : un en Kotlin et un en Java. Ouvrez le projet de votre choix. Les extraits de code sont fournis dans les deux langages.

Créer des appareils virtuels

Si vous ne disposez pas d'un téléphone Android, d'une petite tablette ou d'une grande tablette avec un niveau d'API 35 ou supérieur, ouvrez le gestionnaire d'appareils dans Android Studio et créez les appareils virtuels suivants dont vous avez besoin parmi ceux ci-dessous :

  • Téléphone : Pixel 8, niveau d'API 35 ou supérieur
  • Tablette : Pixel Tablet, niveau d'API 35 ou supérieur

3. Exécuter l'application

L'application exemple affiche une liste d'éléments. Lorsque l'utilisateur sélectionne un de ces éléments, l'application affiche des informations le concernant.

L'application comporte trois activités :

  • ListActivity : contient une liste d'éléments dans une RecyclerView.
  • DetailActivity : affiche les informations sur l'élément sélectionné dans la liste.
  • SummaryActivity : affiche un résumé des informations lorsque l'élément "Summary" (Résumé) est sélectionné dans la liste.

Continuer à partir de l'atelier de programmation précédent

Dans l'atelier de programmation Créer une mise en page de type "Liste/Détail" avec l'intégration d'activités et Material Design, nous avons développé une application avec une vue de type "Liste/Détail" utilisant l'intégration d'activités, avec une navigation facilitée par un rail de navigation et une barre de navigation inférieure.

  1. Exécutez l'application sur une grande tablette ou l'émulateur Pixel en mode portrait. L'écran de liste principal et une barre de navigation s'affichent en bas.

74906232acad76f.png

  1. Faites pivoter la tablette en mode paysage. L'écran doit se diviser pour afficher la liste d'un côté et les détails de l'autre. La barre de navigation en bas de l'écran doit être remplacée par un rail de navigation vertical.

dc6a7d1c02c49cd4.png

Nouvelles fonctionnalités avec l'intégration d'activités

Prêt à passer à la vitesse supérieure avec la mise en page à double volet ? Dans cet atelier de programmation, nous allons ajouter de nouvelles fonctionnalités intéressantes afin d'améliorer l'expérience de vos utilisateurs. Voici ce que nous allons créer :

  1. Rendons ces volets dynamiques ! Nous allons implémenter l'expansion des volets, ce qui permettra à vos utilisateurs de redimensionner (ou d'agrandir) les volets pour une vue personnalisée.

2ec5f7fd6df5d8cd.gif

  1. Donnez à vos utilisateurs la possibilité de définir des priorités ! Grâce à l'épinglage des activités, les utilisateurs peuvent garder en permanence leurs tâches les plus importantes à l'écran.

980d0033972737ed.gif

  1. Besoin de vous concentrer sur une tâche spécifique ? Nous allons ajouter une fonctionnalité d'assombrissement en plein écran pour atténuer légèrement les distractions et permettre aux utilisateurs de se concentrer sur ce qui compte le plus.

2d3455e0f8901f95.png

4. Expansion des volets

Lorsque vous utilisez une mise en page à double volet sur un grand écran, les utilisateurs doivent souvent se concentrer sur l'un des volets tout en gardant l'autre à l'écran. Par exemple, ils peuvent lire des articles d'un côté tout en gardant une liste de conversations de chat de l'autre. Il est courant que les utilisateurs souhaitent redimensionner les volets pour pouvoir se concentrer sur une activité.

Pour ce faire, l'intégration d'activités ajoute une nouvelle API qui vous permet d'octroyer aux utilisateurs la possibilité de modifier le ratio de fractionnement et de personnaliser la transition de redimensionnement.

Ajouter une dépendance

Commencez par ajouter WindowManager 1.4 à votre fichier build.gradle.

Remarque : Certaines fonctionnalités de cette bibliothèque ne fonctionnent que sur Android 15 (niveau d'API 35) ou version ultérieure.

build.gradle

 implementation 'androidx.window:window:1.4.0-alpha02'

Personnaliser le séparateur de fenêtre

Créez une instance DividerAttributes et ajoutez-la à SplitAttributes. Cet objet configure le comportement général de votre mise en page fractionnée. Vous pouvez utiliser les propriétés de couleur, de largeur et de plage de déplacement de DividerAttributes afin d'améliorer l'expérience utilisateur.

Personnaliser le séparateur :

  1. Vérifiez le niveau d'API des extensions WindowManager. Étant donné que la fonctionnalité d'expansion des volets n'est disponible qu'à partir du niveau d'API 6, elle s'applique également aux nouvelles fonctionnalités restantes.
  2. Créez DividerAttributes : pour styliser le séparateur entre vos volets, créez un objet DividerAttributes. Cet objet vous permet de définir les éléments suivants :
  • color : modifiez la couleur du séparateur pour qu'il corresponde au thème de votre application ou créez une séparation visuelle.
  • widthDp : ajustez la largeur du séparateur pour une meilleure visibilité ou un aspect plus subtil.
  1. Ajouter à SplitAttributes : une fois que vous avez personnalisé votre séparateur, ajoutez-le à votre objet DividerAttributes.
  2. Définir la plage de déplacement (facultatif) : vous pouvez également contrôler la distance à laquelle les utilisateurs peuvent déplacer le séparateur pour redimensionner les volets.
  • DRAG_RANGE_SYSTEM_DEFAULT : utilisez cette valeur spéciale pour permettre au système de déterminer une plage de déplacement appropriée en fonction de la taille de l'écran et du facteur de forme de l'appareil.
  • Valeur personnalisée (entre 0,33 et 0,66) : définissez votre propre plage de déplacement pour limiter la possibilité pour les utilisateurs de redimensionner les volets. N'oubliez pas que si l'utilisateur dépasse cette limite, la mise en page fractionnée sera désactivée.

Remplacez splitAttributes par le code suivant.

SplitManager.kt

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

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

Créez divider_color.xml dans le dossier res/color avec le contenu suivant.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:color="#669df6" />
</selector>

Exécuter le code

Et voilà ! Créer et exécuter l'application exemple

L'expansion des volets devrait apparaître et vous devriez pouvoir le faire glisser.

2ec5f7fd6df5d8cd.gif

Modifier le ratio de fractionnement dans les anciennes versions

Remarque importante concernant la compatibilité : La fonctionnalité d'expansion des volets n'est disponible que sur les extensions WindowManager 6 ou version ultérieure. Vous devez donc disposer d'Android 15 (niveau d'API 35) ou version ultérieure.

Toutefois, vous devez toujours offrir une bonne expérience aux utilisateurs d'anciennes versions d'Android.

Sur Android 14 (niveau d'API 34) ou version antérieure, vous pouvez toujours fournir des ajustements dynamiques du ratio de fractionnement à l'aide de la classe SplitAttributesCalculator. Cela permet de conserver un certain niveau de contrôle de la mise en page par l'utilisateur, même sans l'expansion des volets.

a36f8ba4226353c5.gif

Vous voulez savoir comment utiliser ces fonctionnalités de manière optimale ? Nous aborderons toutes les bonnes pratiques et les conseils d'experts dans la section "Bonnes pratiques".

5. Épinglage d'activités

Avez-vous déjà voulu garder une partie de votre écran partagé fixe tout en naviguant librement dans l'autre ? Imaginez que vous lisez un long article d'un côté tout en pouvant interagir avec d'autres contenus d'application de l'autre côté.

C'est là que l'épinglage d'activités entre en jeu. Il vous permet d'épingler l'une des fenêtres fractionnées pour qu'elle reste à l'écran même lorsque vous naviguez dans l'autre fenêtre. Vos utilisateurs bénéficient ainsi d'une expérience multitâche plus ciblée et productive.

Ajouter le bouton d'épinglage

Commençons par ajouter un bouton dans DetailActivity.. L'application épinglera ce DetailActivity lorsque les utilisateurs cliqueront sur le bouton.

Apportez les modifications suivantes à activity_detail.xml :

  1. Ajoutez un identifiant à ConstraintLayout.
android:id="@+id/detailActivity"
  1. Ajoutez un bouton en bas de la mise en page
<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"/>
  1. Appliquez une contrainte à la partie inférieure de TextView pour qu'elle se trouve en haut du bouton.
app:layout_constraintBottom_toTopOf="@id/pinButton"

Supprimez cette ligne dans TextView.

app:layout_constraintBottom_toBottomOf="parent"

Voici le code XML complet de votre fichier de mise en page activity_detail.xml, y compris le bouton PIN THIS ACTIVITY (ÉPINGLER CETTE ACTIVITÉ) que nous venons d'ajouter :

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

Ajoutez la chaîne pin_this_activity à res/values/strings.xml.

<string name="pin_this_activity">PIN THIS ACTIVITY</string>

Connecter le bouton d'épinglage

  1. Déclarer la variable : dans votre fichier DetailActivity.kt, déclarez une variable pour contenir une référence au bouton PIN THIS ACTIVITY (ÉPINGLER CETTE ACTIVITÉ) :

DetailActivity.kt

private lateinit var pinButton: Button

DetailActivity.java

private Button pinButton;
  1. Recherchez le bouton dans la mise en page et ajoutez un rappel setOnClickListener().

DetailActivity.kt/onCreate

pinButton = findViewById(R.id.pinButton)
pinButton.setOnClickListener {
 pinActivityStackExample(taskId)
}

DetailActivity.java/onCreate()

Button pinButton = findViewById(R.id.pinButton);
pinButton.setOnClickListener( (view) => {
        pinActivityStack(getTaskId());

});
  1. Créez une méthode appelée pinActivityStackExample dans votre classe DetailActivity. Nous allons implémenter la véritable logique d'épinglage ici.

DetailActivity.kt

private fun pinActivityStackExample(taskId: Int) {

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

DetailActivity.java

private void pinActivityStackExample(int taskId) {
    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(taskId, pinSplitRule);
}

Remarque :

  1. Vous ne pouvez épingler qu'une seule activité à la fois. Retirez l'activité épinglée avec
unpinTopActivityStack()

avant d'en épingler une autre.

  1. Pour activer l'expansion des volets lorsque vous épinglez l'activité, appelez
setDividerAttributes()

pour le nouveau

SplitAttributes

également.

Modifications du retour en arrière

Avec WindowManager 1.4, le comportement du retour en arrière a changé. Si vous utilisez un bouton, l'événement "Retour" est envoyé à la dernière activité sélectionnée.

Navigation à boutons :

  • Avec la navigation par bouton, l'événement "Retour" est désormais envoyé de manière cohérente à la dernière activité sélectionnée. Cela simplifie le comportement du retour en arrière, ce qui le rend plus prévisible pour les utilisateurs.

Navigation par gestes :

  • Android 14 (niveau d'API 34) ou version antérieure : le geste Retour envoie l'événement à l'activité où il a eu lieu, ce qui peut entraîner un comportement inattendu dans les scénarios d'écran partagé.
  • Android 15 (niveau d'API 35) ou version ultérieure :
  • Activités dans la même application : le geste Retour arrête systématiquement l'activité principale, quelle que soit la direction du balayage, offrant ainsi une expérience plus unifiée.
  • Activités dans différentes applications (superposition) : l'événement "Retour" renvoie à la dernière activité sélectionnée, ce qui correspond au comportement de la navigation par bouton.

Exécuter le code

Créer et exécuter l'application exemple

Épingler l'activité

  • Accédez à l'écran DetailActivity.
  • Appuyez sur le bouton PIN THIS ACTIVITY (ÉPINGLER CETTE ACTIVITÉ).

980d0033972737ed.gif

6. Diminuer la luminosité des boîtes de dialogue en plein écran

Bien que l'intégration d'activités facilite les mises en page en écran partagé, les boîtes de dialogue des versions précédentes diminuaient uniquement la luminosité du conteneur de leur propre activité. Cela peut créer une expérience visuelle décousue, en particulier lorsque vous souhaitez que la boîte de dialogue soit au centre de l'attention.

Solution : WindowManager 1.4

  • Aucun problème. Avec WindowManager 1.4, les boîtes de dialogue diminuent désormais la luminosité de l'intégralité de la fenêtre de l'application par défaut (DimAreaBehavior.Companion.ON_TASK), ce qui offre une expérience plus immersive et plus ciblée.
  • Vous avez besoin de rétablir l'ancien comportement ? Nous avons probablement la réponse ! Vous pouvez toujours choisir de diminuer uniquement la luminosité du conteneur de l'activité à l'aide de ON_ACTIVITY_STACK.

ON_ACTIVITY_STACK

ON_TASK

Voici comment utiliser ActivityEmbeddingController pour gérer le comportement de diminution de la luminosité en plein écran :

Remarque : La diminution de la luminosité de la boîte de dialogue en plein écran est disponible avec les extensions WindowManager 5 ou version ultérieure.

SplitManager.kt/createSplit()

with(ActivityEmbeddingController.getInstance(context)) {
   if (WindowSdkExtensions.getInstance().extensionVersion  >= 5) {
       setEmbeddingConfiguration(
           EmbeddingConfiguration.Builder()
               .setDimAreaBehavior(ON_TASK)
               .build()
       )
   }
}

SplitManager.java/createSplit()

ActivityEmbeddingController controller = ActivityEmbeddingController.getInstance(context);
if (WindowSdkExtensions.getInstance().getExtensionVersion()  >= 5) {
    controller.setEmbeddingConfiguration(
        new EmbeddingConfiguration.Builder()
            .setDimAreaBehavior(EmbeddingConfiguration.DimAreaBehavior.ON_TASK)
            .build()
    );
}

Pour présenter la fonctionnalité de diminution de la luminosité en plein écran, nous allons ajouter une boîte de dialogue d'alerte qui invite l'utilisateur à confirmer avant d'épingler l'activité. Lorsque cette boîte de dialogue s'affiche, elle diminue la luminosité de l'intégralité de la fenêtre de l'application, et pas seulement le conteneur où se trouve l'activité.

DetailActivity.kt

pinButton.setOnClickListener {
 showAlertDialog(taskId)
}

...
private fun showAlertDialog(taskId: Int) {
 val builder = AlertDialog.Builder(this)
 builder.setTitle(getString(R.string.dialog_title))
 builder.setMessage(getString(R.string.dialog_message))
 builder.setPositiveButton(getString(R.string.button_yes)) { _, _ ->
   if (WindowSdkExtensions.getInstance().extensionVersion  >= 6) {
     pinActivityStackExample(taskId)
   }
 }
 builder.setNegativeButton(getString(R.string.button_cancel)) { _, _ ->
   // Cancel
 }
 val dialog: AlertDialog = builder.create()
 dialog.show()
}

DetailActivity.java

pinButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       showAlertDialog(getTaskId());
   }
});

...

private void showAlertDialog(int taskId) {
   AlertDialog.Builder builder = new AlertDialog.Builder(this);
   builder.setTitle(getString(R.string.dialog_title));
   builder.setMessage(getString(R.string.dialog_message));

   builder.setPositiveButton(getString(R.string.button_yes), new DialogInterface.OnClickListener() {
       @Override
       public void onClick(DialogInterface dialog, int which) {
           if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
               pinActivityStackExample(taskId);
           }
       }
   });
   builder.setNegativeButton(getString(R.string.button_cancel), new DialogInterface.OnClickListener() {
       @Override
       public void onClick(DialogInterface dialog, int which) {
           // Cancel
       }
   });
   AlertDialog dialog = builder.create();
   dialog.show();
}

Ajoutez les chaînes suivantes à res/values/strings.xml.

<!-- Dialog information -->
<string name="dialog_title">Activity Pinning</string>
<string name="dialog_message">Confirm to pin this activity</string>
<string name="button_yes">Yes</string>
<string name="button_cancel">Cancel</string>

Exécuter le code

Créer et exécuter l'application exemple

Cliquer sur le bouton "Pin this activity" (épingler cette activité) :

  • Une boîte de dialogue d'alerte s'affiche et vous invite à confirmer l'action d'épinglage.
  • Notez que la luminosité de l'intégralité de l'écran, y compris les deux volets, est diminuée, ce qui permet de concentrer l'attention sur la boîte de dialogue.

2d3455e0f8901f95.png

7. Bonnes pratiques

Autoriser les utilisateurs à désactiver la mise en page à double volet

Pour faciliter la transition vers les nouvelles mises en page, octroyons aux utilisateurs la possibilité de basculer entre les vues à double volet et à une seule colonne. Pour ce faire, nous allons utiliser SplitAttributesCalculator et SharedPreferences afin de stocker les préférences utilisateur.

Modifier le ratio de fractionnement sur Android 14 ou version antérieure

Nous avons étudié l'expansion des volets, qui permet aux utilisateurs d'ajuster le ratio de fractionnement sur Android 15 et versions ultérieures. Mais comment pouvons-nous offrir un niveau de flexibilité similaire aux utilisateurs d'anciennes versions d'Android ?

Voyons comment SplitAttributesCalculator peut nous aider à y parvenir et à garantir une expérience cohérente sur un plus grand nombre d'appareils.

Voici un exemple d'affichage :

a87452341434c86d.gif

Créer un écran de paramètres

Pour commencer, créons un écran de paramètres dédié à la configuration utilisateur.

Sur cet écran de paramètres, nous allons intégrer un bouton pour activer ou désactiver la fonctionnalité d'intégration d'activités pour l'ensemble de l'application. Nous inclurons également une barre de progression qui permettra aux utilisateurs d'ajuster le ratio de fractionnement de la mise en page à double volet. Notez que la valeur du ratio de fractionnement ne sera appliquée que si l'option d'intégration d'activités est activée.

Une fois que l'utilisateur a défini des valeurs dans SettingsActivity, nous les enregistrons dans SharedPreferences pour les utiliser ultérieurement dans d'autres parties de l'application.

build.gradle

Ajoutez une dépendance de préférences.

implementation 'androidx.preference:preference-ktx:1.2.1' // Kotlin

Ou

implementation 'androidx.preference:preference:1.2.1' // Java

SettingsActivity.kt

package com.example.activity_embedding

import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat

class SettingsActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.settings_activity)
    if (savedInstanceState == null) {
      supportFragmentManager
        .beginTransaction()
        .replace(R.id.settings, SettingsFragment())
        .commit()
    }
    supportActionBar?.setDisplayHomeAsUpEnabled(true)
  }

  override fun onOptionsItemSelected(item: MenuItem): Boolean {
    if (item.itemId == android.R.id.home) finishActivity()
    return super.onOptionsItemSelected(item)
  }

  private fun finishActivity() { finish() }

  class SettingsFragment : PreferenceFragmentCompat() {
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
      setPreferencesFromResource(R.xml.root_preferences, rootKey)
findPreference<SwitchPreferenceCompat>("dual_pane")?.setOnPreferenceChangeListener { _, newValue ->
        if (newValue as Boolean) {
          this.activity?.let {
            SharePref(it.applicationContext).setAEFlag(true)
          }
        } else {
          this.activity?.let {
            SharePref(it.applicationContext).setAEFlag(false)
          }
        }
        this.activity?.finish()
        true
      }

      val splitRatioPreference: SeekBarPreference? = findPreference("split_ratio")
      splitRatioPreference?.setOnPreferenceChangeListener { _, newValue ->
        if (newValue is Int) {
          this.activity?.let { SharePref(it.applicationContext).setSplitRatio(newValue.toFloat()/100) }
        }
        true
      }
    }
  }
}

SettingsActivity.java

package com.example.activity_embedding;

import android.os.Bundle;
import android.view.MenuItem;

import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreferenceCompat;

public class SettingsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.settings_activity);
        if (savedInstanceState == null) {
            getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.settings, new SettingsFragment())
                .commit();
        }
        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finishActivity();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void finishActivity() {
        finish();
    }

    public static class SettingsFragment extends PreferenceFragmentCompat {
        @Override
        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
            setPreferencesFromResource(R.xml.root_preferences, rootKey);

            SwitchPreferenceCompat dualPanePreference = findPreference("dual_pane");
            if (dualPanePreference != null) {
                dualPanePreference.setOnPreferenceChangeListener((preference, newValue) -> {
                    boolean isDualPane = (Boolean) newValue;
                    if (getActivity() != null) {
                        SharePref sharePref = new SharePref(getActivity().getApplicationContext());
                        sharePref.setAEFlag(isDualPane);
                        getActivity().finish();
                    }
                    return true;
                });
            }

            SeekBarPreference splitRatioPreference = findPreference("split_ratio");
            if (splitRatioPreference != null) {
                splitRatioPreference.setOnPreferenceChangeListener((preference, newValue) -> {
                    if (newValue instanceof Integer) {
                        float splitRatio = ((Integer) newValue) / 100f;
                        if (getActivity() != null) {
                            SharePref sharePref = new SharePref(getActivity().getApplicationContext());
                            sharePref.setSplitRatio(splitRatio);
                        }
                    }
                    return true;
                });
            }
        }
    }
}

Ajouter settings_activity.xml dans le dossier de mise en page

settings_activity.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <FrameLayout
       android:id="@+id/settings"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />
</LinearLayout>

Ajoutez SettingsActivity à votre fichier manifeste.

<activity
   android:name=".SettingsActivity"
   android:exported="false"
   android:label="@string/title_activity_settings" />

Configurez les règles de fractionnement pour SettingsActivity.

SplitManager.kt/createSplit()

val settingActivityFilter = ActivityFilter(
   ComponentName(context, SettingsActivity::class.java),
   null
)
val settingActivityFilterSet = setOf(settingActivityFilter)
val settingActivityRule = ActivityRule.Builder(settingActivityFilterSet)
   .setAlwaysExpand(true)
   .build()
ruleController.addRule(settingActivityRule)

SplitManager.java/createSplit()

Set<ActivityFilter> settingActivityFilterSet = new HashSet<>();
ActivityFilter settingActivityFilter = new ActivityFilter(
        new ComponentName(context, SettingsActivity.class),
        null
);
settingActivityFilterSet.add(settingActivityFilter);
ActivityRule settingActivityRule = new ActivityRule.Builder(settingActivityFilterSet)
        .setAlwaysExpand(true).build();
ruleController.addRule(settingActivityRule);

Voici le code permettant d'enregistrer les paramètres utilisateur dans SharedPreferences.

SharedPref.kt

package com.example.activity_embedding

import android.content.Context
import android.content.SharedPreferences

class SharePref(context: Context) {
    private val sharedPreferences: SharedPreferences =
        context.getSharedPreferences("my_app_preferences", Context.MODE_PRIVATE)

    companion object {
        private const val AE_FLAG = "is_activity_embedding_enabled"
        private const val SPLIT_RATIO = "activity_embedding_split_ratio"
        const val DEFAULT_SPLIT_RATIO = 0.3f
    }

    fun setAEFlag(isEnabled: Boolean) {
        sharedPreferences.edit().putBoolean(AE_FLAG, isEnabled).apply()
    }

    fun getAEFlag(): Boolean = sharedPreferences.getBoolean(AE_FLAG, true)

    fun getSplitRatio(): Float = sharedPreferences.getFloat(SPLIT_RATIO, DEFAULT_SPLIT_RATIO)

    fun setSplitRatio(ratio: Float) {
        sharedPreferences.edit().putFloat(SPLIT_RATIO, ratio).apply()
    }
}

SharedPref.java

package com.example.activity_embedding;

import android.content.Context;
import android.content.SharedPreferences;

public class SharePref {
    private static final String PREF_NAME = "my_app_preferences";
    private static final String AE_FLAG = "is_activity_embedding_enabled";
    private static final String SPLIT_RATIO = "activity_embedding_split_ratio";
    public static final float DEFAULT_SPLIT_RATIO = 0.3f;

    private final SharedPreferences sharedPreferences;

    public SharePref(Context context) {
        this.sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
    }

    public void setAEFlag(boolean isEnabled) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putBoolean(AE_FLAG, isEnabled);
        editor.apply();
    }

    public boolean getAEFlag() {
        return sharedPreferences.getBoolean(AE_FLAG, true);
    }

    public float getSplitRatio() {
        return sharedPreferences.getFloat(SPLIT_RATIO, DEFAULT_SPLIT_RATIO);
    }

    public void setSplitRatio(float ratio) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putFloat(SPLIT_RATIO, ratio);
        editor.apply();
    }
}

Vous avez également besoin d'un fichier XML de mise en page de l'écran de préférences. Créez root_preferences.xml sous res/xml avec le code suivant.

<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android">
   <PreferenceCategory app:title="@string/split_setting_header">

       <SwitchPreferenceCompat
           app:key="dual_pane"
           app:title="@string/dual_pane_title" />

       <SeekBarPreference
           app:key="split_ratio"
           app:title="@string/split_ratio_title"
           android:min="0"
           android:max="100"
           app:defaultValue="50"
           app:showSeekBarValue="true" />
   </PreferenceCategory>
</PreferenceScreen>

Ajoutez ensuite le code suivant à res/values/strings.xml.

<string name="title_activity_settings">SettingsActivity</string>
<string name="split_setting_header">Dual Pane Display</string>
<string name="dual_pane_title">Dual Pane</string>
<string name="split_ratio_title">Split Ratio</string>

Ajouter SettingsActivity au menu

Associons notre SettingsActivity nouvellement créé à une destination de navigation afin que les utilisateurs puissent y accéder facilement depuis l'interface principale de l'application.

  1. Dans votre fichier ListActivity, déclarez des variables pour la barre de navigation inférieure et le rail de navigation de gauche :

ListActivity.kt

 private lateinit var navRail: NavigationRailView private lateinit var bottomNav: BottomNavigationView

ListActivity.java

 private NavigationRailView navRail;  private BottomNavigationView bottomNav;
  1. Dans la méthode onCreate() de votre ListActivity, utilisez findViewById pour associer ces variables aux vues correspondantes de votre mise en page.
  2. Ajoutez un OnItemSelectedListener à la fois à la barre de navigation inférieure et au rail de navigation pour gérer les événements de sélection d'éléments :

ListActivity.kt/onCreate()

navRail  = findViewById(R.id.navigationRailView)
bottomNav = findViewById(R.id.bottomNavigationView)

val menuListener = NavigationBarView.OnItemSelectedListener { item ->
    when (item.itemId) {
        R.id.navigation_home -> {
            true
        }
        R.id.navigation_dashboard -> {
            true
        }
        R.id.navigation_settings -> {
            startActivity(Intent(this, SettingsActivity::class.java))
            true
        }
        else -> false
    }
}

navRail.setOnItemSelectedListener(menuListener)
bottomNav.setOnItemSelectedListener(menuListener)

ListActivity.java/onCreate()

NavigationRailView navRail = findViewById(R.id.navigationRailView);
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigationView);

NavigationBarView.OnItemSelectedListener menuListener = new NavigationBarView.OnItemSelectedListener() {
   @Override
   public boolean onNavigationItemSelected(@NonNull MenuItem item) {
       switch (item.getItemId()) {
           case R.id.navigation_home:
               // Handle navigation_home selection
               return true;
           case R.id.navigation_dashboard:
               // Handle navigation_dashboard selection
               return true;
           case R.id.navigation_settings:
               startActivity(new Intent(ListActivity.this, SettingsActivity.class));
               return true;
           default:
               return false;
       }
   }
};

navRail.setOnItemSelectedListener(menuListener);
bottomNav.setOnItemSelectedListener(menuListener);

L'application lit SharedPreferences et l'affiche en mode écran partagé ou en mode SPLIT_TYPE_EXPAND.

  • Lorsque la configuration de la fenêtre change, le programme vérifie si la contrainte de fenêtre fractionnée est respectée (si la largeur est supérieure à 840 dp).
  • L'application vérifie la valeur SharedPreferences pour voir si l'utilisateur a activé la fenêtre fractionnée pour l'affichage. Sinon, elle renvoie SplitAttribute avec le type SPLIT_TYPE_EXPAND.
  • Si la fenêtre fractionnée est activée, l'application lit la valeur SharedPreferences pour obtenir le ratio de fractionnement. Cette méthode ne fonctionne que lorsque la version de WindowSDKExtensions est inférieure à 6, car la version 6 prend déjà en charge l'expansion des volets et ignore le paramètre du ratio de fractionnement. À la place, les développeurs peuvent autoriser les utilisateurs à faire glisser le séparateur dans l'interface utilisateur.

ListActivity.kt/onCreate()

...

SplitController.getInstance(this).setSplitAttributesCalculator{
       params -> params.defaultSplitAttributes
   if (params.areDefaultConstraintsSatisfied) {
       setWiderScreenNavigation(true)

       if (SharePref(this.applicationContext).getAEFlag()) {
           if (WindowSdkExtensions.getInstance().extensionVersion  < 6) {
               // Read a dynamic split ratio from shared preference.
               val currentSplit = SharePref(this.applicationContext).getSplitRatio()
               if (currentSplit != SharePref.DEFAULT_SPLIT_RATIO) {
                   return@setSplitAttributesCalculator SplitAttributes.Builder()
                       .setSplitType(SplitAttributes.SplitType.ratio(SharePref(this.applicationContext).getSplitRatio()))
                     .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
                       .build()
               }
           }
           return@setSplitAttributesCalculator params.defaultSplitAttributes
       } else {
           SplitAttributes.Builder()
               .setSplitType(SPLIT_TYPE_EXPAND)
               .build()
       }
   } else {
       setWiderScreenNavigation(false)
       SplitAttributes.Builder()
           .setSplitType(SPLIT_TYPE_EXPAND)
           .build()
   }
}

...

ListActivity.java/onCreate()

...
SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
   if (params.areDefaultConstraintsSatisfied()) {
       setWiderScreenNavigation(true);

       SharePref sharedPreference = new SharePref(this.getApplicationContext());
       if (sharedPreference.getAEFlag()) {
           if (WindowSdkExtensions.getInstance().getExtensionVersion()  < 6) {
               // Read a dynamic split ratio from shared preference.
               float currentSplit = sharedPreference.getSplitRatio();
               if (currentSplit != SharePref.DEFAULT_SPLIT_RATIO) {
                   return new SplitAttributes.Builder()
                           .setSplitType(SplitAttributes.SplitType.ratio(sharedPreference.getSplitRatio()))
                           .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
                           .build();
               }
           }
           return params.getDefaultSplitAttributes();
       } else {
           return new SplitAttributes.Builder()
                   .setSplitType(SPLIT_TYPE_EXPAND)
                   .build();
       }
   } else {
       setWiderScreenNavigation(false);
       return new SplitAttributes.Builder()
               .setSplitType(SPLIT_TYPE_EXPAND)
               .build();
   }
});

...

Pour déclencher SplitAttributesCalculator après la modification des paramètres, nous devons invalider les attributs actuels. Pour ce faire, nous appelons invalidateVisibleActivityStacks() à partir de ActivityEmbeddingController;. Avant WindowManager 1.4, la méthode est appelée

invalidateTopVisibleSplitAttributes.

ListActivity.kt/onResume()

override fun onResume() {
   super.onResume()
   ActivityEmbeddingController.getInstance(this).invalidateVisibleActivityStacks()
}

ListActivity.java/onResume()

@Override
public void onResume() {
    super.onResume();
    ActivityEmbeddingController.getInstance(this).invalidateVisibleActivityStacks();
}

Exécuter le code

Créer et exécuter l'application exemple

Explorer les paramètres :

  • Accédez à l'écran des paramètres.
  • Activez ou désactivez le bouton Activer la fenêtre fractionnée.
  • Ajustez le curseur du ratio de fractionnement (s'il est disponible sur votre appareil).

Observez les modifications apportées à la mise en page :

  • Sur les appareils équipés d'Android 14 ou version antérieure : la mise en page doit passer du mode à un seul volet à celui à double volet en fonction du bouton, et le ratio de fractionnement doit changer lorsque vous ajustez le curseur.
  • Sur les appareils équipés d'Android 15 ou version ultérieure : l'expansion des volets devrait vous permettre de les redimensionner de manière dynamique, quel que soit le paramètre du curseur.

8. Félicitations !

Bravo ! Vous avez amélioré votre application en ajoutant de nouvelles fonctionnalités puissantes grâce à l'intégration d'activités et à WindowManager. Vos utilisateurs bénéficieront désormais d'une expérience plus flexible, intuitive et engageante sur les grands écrans, quelle que soit la version d'Android.

9. En savoir plus