Prendre en charge le fenêtrage de bureau

Le fenêtrage du bureau permet aux utilisateurs d'exécuter plusieurs applications simultanément dans des fenêtres d'application redimensionnables pour une expérience polyvalente semblable à celle d'un ordinateur.

La figure 1 illustre l'organisation de l'écran lorsque le fenêtrage du bureau est activé. Points à noter :

  • Les utilisateurs peuvent exécuter plusieurs applications côte à côte simultanément.
  • La barre des tâches est en position fixe en bas de l'écran et affiche les applications en cours d'exécution. Les utilisateurs peuvent épingler des applications pour y accéder rapidement.
  • Une nouvelle barre d'en-tête personnalisable décore le haut de chaque fenêtre avec des commandes telles que "Réduire" et "Agrandir".
Figure 1 : Fenêtrage du bureau sur une tablette.

Par défaut, les applications s'ouvrent en plein écran sur les tablettes Android. Pour lancer une application en mode fenêtré, appuyez de manière prolongée sur la poignée de la fenêtre en haut de l'écran et faites-la glisser dans l'UI, comme illustré dans la figure 2.

Lorsqu'une application est ouverte en mode fenêtrage du bureau, les autres applications s'ouvrent également dans des fenêtres de bureau.

Figure 2 : Appuyez de manière prolongée sur la poignée de la fenêtre de l'application et faites-la glisser pour activer le fenêtrage du bureau.

Les utilisateurs peuvent également appeler le fenêtrage du bureau à partir du menu qui s'affiche sous le handle de la fenêtre lorsqu'ils appuient ou cliquent sur le handle, ou en utilisant le raccourci clavier Touche Meta (Windows, Commande ou Recherche) + Ctrl + Flèche vers le bas.

Les utilisateurs quittent le mode fenêtré sur ordinateur en fermant toutes les fenêtres actives ou en saisissant le gestionnaire de fenêtres en haut d'une fenêtre de bureau et en faisant glisser l'application en haut de l'écran. Le raccourci clavier Meta+H permet également de quitter le mode fenêtré et d'exécuter à nouveau les applications en plein écran.

Pour revenir à l'affichage des fenêtres sur le bureau, appuyez ou cliquez sur la vignette de l'espace de bureau dans l'écran "Récents".

Redimensionnement et mode de compatibilité

Dans le fenêtrage du bureau, les applications dont l'orientation est verrouillée sont librement redimensionnables. Cela signifie que même si une activité est verrouillée en orientation portrait, les utilisateurs peuvent toujours redimensionner l'application en une fenêtre d'orientation paysage.

Figure 3 : Redimensionnement de la fenêtre d'une application limitée au mode portrait en mode paysage.

Les applications déclarées comme non redimensionnables (c'est-à-dire resizeableActivity = false) voient leur UI mise à l'échelle tout en conservant les mêmes proportions.

Figure 4 : L'UI d'une application non redimensionnable est mise à l'échelle lorsque la fenêtre est redimensionnée.

Les applications d'appareil photo qui verrouillent l'orientation ou sont déclarées comme non redimensionnables bénéficient d'un traitement spécial pour leur viseur : la fenêtre est entièrement redimensionnable, mais le viseur conserve le même format. En supposant que les applications s'exécutent toujours en mode Portrait ou Paysage, elles codent en dur ou font des hypothèses qui entraînent des erreurs de calcul de l'orientation ou du format de l'aperçu ou de l'image capturée, ce qui donne des images étirées, à l'envers ou de travers.

Tant que les applications ne sont pas prêtes à implémenter des viseurs d'appareil photo entièrement responsifs, le traitement spécial offre une expérience utilisateur plus basique qui atténue les effets que de mauvaises hypothèses peuvent entraîner.

Pour en savoir plus sur le mode de compatibilité des applications de caméras, consultez Mode de compatibilité avec les appareils.

Figure 5 : Le viseur de la caméra conserve ses proportions lorsque la fenêtre est redimensionnée.

Encarts d'en-tête personnalisables

Toutes les applications exécutées dans le fenêtrage du bureau disposent d'une barre d'en-tête, même en mode immersif. Vous pouvez personnaliser cette barre pour éviter que le contenu de votre application ne soit masqué et pour dessiner des éléments d'interface utilisateur personnalisés directement dans l'espace d'en-tête.

Chrome avant et après l'implémentation d'en-têtes personnalisés.
Figure 6. Chrome avant et après l'implémentation d'en-têtes personnalisés.

Implémentation

Pour dessiner du contenu personnalisé dans la barre d'en-tête, la première étape consiste à rendre l'arrière-plan de la barre d'en-tête transparent. Pour ce faire, utilisez l'indicateur APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND avec WindowInsetsController.

window.insetsController?.setSystemBarsAppearance(
    WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
    WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
)

Une fois la barre d'en-tête transparente, vous pouvez styliser la zone d'en-tête pour l'adapter à la conception de votre application. Utilisez WindowInsets.isCaptionBarVisible pour détecter si la barre est présente et appliquer la hauteur ou la marge intérieure appropriée à votre mise en page.

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun CaptionBar() {
    if (WindowInsets.isCaptionBarVisible) {
        Row(
            modifier = Modifier
                .windowInsetsTopHeight(WindowInsets.captionBar)
                .fillMaxWidth()
                .background(if (isSystemInDarkTheme()) Color.White else Color.Black),
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = "Caption Bar Title",
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier.padding(4.dp)
            )
        }
    }
}

  • setSystemBarsAppearance(appearance,mask) : configure le style visuel des barres système. Le premier paramètre définit les indicateurs d'apparence cible, tandis que le second sert de masque pour contrôler les indicateurs spécifiques qui sont modifiés.

  • windowInsetsTopHeight() : définit automatiquement la hauteur de votre composable pour qu'elle corresponde à la barre d'en-tête du système, ce qui permet à votre arrière-plan personnalisé de remplir la zone de légende sans coder en dur les valeurs en pixels.

  • WindowInsets.captionBar : fournit les dimensions des commandes de fenêtrage du bureau (Fermer, Agrandir, etc.), ce qui permet à votre UI de s'adapter ou de se masquer automatiquement lorsque vous entrez dans le fenêtrage du bureau ou que vous en sortez.

Pour en savoir plus, consultez À propos des encarts de fenêtre. En plus d'un titre, vous pouvez afficher d'autres éléments d'interface utilisateur dans la barre de légende, tels que des onglets (comme dans Google Chrome), des barres de recherche ou des avatars de profil.

Interface utilisateur

Pour éviter que votre UI ne se chevauche avec les boutons système, Android 15 fournit la méthode WindowInsets#getBoundingRects(). La méthode renvoie une liste d'objets Rect représentant les zones occupées par les éléments système. Tout espace restant dans la barre de légende est une zone de sécurité où vous pouvez placer du contenu personnalisé sans risque.

Basculez entre l'apparence des éléments de légende du système pour les thèmes clair et sombre à l'aide de APPEARANCE_LIGHT_CAPTION_BARS. Accédez aux encarts à l'aide de WindowInsets.Companion.captionBar() dans Compose ou de WindowInsets.Type.captionBar() dans Views.

Pour en savoir plus, consultez À propos des encarts de fenêtre.

Compatibilité avec le multitâche et les instances multiples

Le multitâche est au cœur du fenêtrage du bureau. Autoriser plusieurs instances de votre application peut considérablement augmenter la productivité des utilisateurs.

À partir d'Android 15, vous pouvez utiliser PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI. En définissant cette propriété dans votre AndroidManifest.xml, vous spécifiez que l'UI système doit fournir des options (comme un bouton "Nouvelle fenêtre") pour que l'application puisse être lancée dans plusieurs instances.

<application>
    <property
        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
        android:value="true" />
</application>

Remarque : Dans les environnements de fenêtrage de bureau et autres environnements multifenêtres, les nouvelles tâches s'ouvrent dans une nouvelle fenêtre. Vérifiez donc le parcours utilisateur chaque fois que votre application démarre plusieurs tâches.

Gérer les instances d'application avec des gestes de déplacement

En mode multifenêtre, les utilisateurs peuvent démarrer une nouvelle instance d'application en faisant glisser un élément d'interface utilisateur (comme un onglet ou un document) hors de la fenêtre de l'application. Les utilisateurs peuvent également déplacer des éléments entre différentes instances de la même application.

Figure 7 : Démarrez une nouvelle instance de Chrome en faisant glisser un onglet hors de la fenêtre du bureau.

Transférer des données par glisser-déposer

Pour configurer un composable en tant que source de déplacement pour le glisser-déposer multi-instances, ce qui permet aux utilisateurs de faire glisser du contenu vers une autre instance de votre application ou de créer une instance en déposant du contenu sur une zone vide de l'écran, utilisez le modificateur dragAndDropSource. Dans son lambda, renvoyez DragAndDropTransferData en transmettant le ClipData contenant les données à transférer et les indicateurs permettant de configurer le comportement multi-instance.

Android 15 introduit deux indicateurs clés pour le fenêtrage de type bureau et les interactions multi-instances :

  • DRAG_FLAG_GLOBAL_SAME_APPLICATION : indique qu'une opération de déplacement peut franchir les limites des fenêtres (pour plusieurs instances de la même application). Lorsque startDragAndDrop() est appelé avec cet indicateur défini, seules les fenêtres visibles appartenant à la même application peuvent participer à l'opération de déplacement et recevoir le contenu déplacé.

Modifier.dragAndDropSource { _ ->
    DragAndDropTransferData(
        clipData = ClipData.newPlainText("label", "Your data"),
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION
    )
}

Modifier.dragAndDropSource { _ ->
    val intent = Intent.makeMainActivity(activity.componentName).apply {
        putExtra("EXTRA_ITEM_ID", itemId)
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or
                Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
                Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
    }

    val pendingIntent = PendingIntent.getActivity(
        activity, 0, intent, PendingIntent.FLAG_IMMUTABLE
    )

    val data = ClipData(
        "Item $itemId",
        arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT),
        ClipData.Item.Builder().setIntentSender(pendingIntent.intentSender).build()
    )

    DragAndDropTransferData(
        clipData = data,
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or
                View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
    )
}

Recevoir les données transférées

Pour accepter les données d'une autre instance, utilisez le modificateur dragAndDropTarget. Vous devez demander explicitement des autorisations si les données proviennent d'une autre instance ou application.

Modifier.dragAndDropTarget(
    shouldStartDragAndDrop = { event ->
        event.toAndroidDragEvent().clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
    },
    target = object : DragAndDropTarget {
        override fun onDrop(event: DragAndDropEvent): Boolean {
            requestDragAndDropPermissions(activity, event.toAndroidDragEvent())
            val clipData = event.toAndroidDragEvent().clipData
            val item = clipData?.getItemAt(0)?.text
            if (item != null) {
                // Process the dropped text item here
            }
            return item != null
        }
    }
)

Étapes clés :

  • Filtre : utilisez shouldStartDragAndDrop pour vérifier si les données entrantes (type MIME) sont acceptées.
  • Autorisations : appelez requestDragAndDropPermissions(event) pour accéder aux données.
  • Gérez l'extraction des données dans le rappel onDrop.

Optimisations supplémentaires

Personnalisez le lancement des applications et la transition des applications du fenêtrage du bureau au plein écran.

Spécifier la taille et la position par défaut

Toutes les applications, même redimensionnables, n'ont pas besoin d'une grande fenêtre pour offrir de la valeur aux utilisateurs. Vous pouvez utiliser la méthode ActivityOptions#setLaunchBounds() pour spécifier une taille et une position par défaut lorsqu'une activité est lancée.

Passer en plein écran depuis l'espace de bureau

Les applications peuvent passer en plein écran en appelant Activity#requestFullScreenMode(). La méthode affiche l'application en plein écran directement à partir de la fenêtre du bureau.