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
- Vous avez terminé 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 savez utiliser Android Studio, y compris la configuration d'appareils virtuels avec Android 15.
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 :
É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.
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 uneRecyclerView
.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.
- 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.
- 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.
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 :
- 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.
- 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.
- 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.
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 :
- 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.
- Créez
DividerAttributes
: pour styliser le séparateur entre vos volets, créez un objetDividerAttributes
. 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.
- Ajouter à
SplitAttributes
: une fois que vous avez personnalisé votre séparateur, ajoutez-le à votre objetDividerAttributes
. - 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.
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.
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
:
- Ajoutez un identifiant à
ConstraintLayout
.
android:id="@+id/detailActivity"
- 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"/>
- 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
- 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;
- 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());
});
- Créez une méthode appelée
pinActivityStackExample
dans votre classeDetailActivity
. 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 :
- Vous ne pouvez épingler qu'une seule activité à la fois. Retirez l'activité épinglée avec
unpinTopActivityStack()
avant d'en épingler une autre.
- 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É).
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
.
|
|
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.
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 :
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.
- 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;
- Dans la méthode
onCreate()
de votreListActivity
, utilisezfindViewById
pour associer ces variables aux vues correspondantes de votre mise en page. - 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 renvoieSplitAttribute
avec le typeSPLIT_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 deWindowSDKExtensions
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
- Guide du développeur : Intégration d'activités
- Documentation de référence : androidx.window.embedding