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 redimensionnables pour une expérience polyvalente semblable à celle d'un ordinateur de bureau.

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 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 fenêtrage du bureau, 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'interface utilisateur, 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 la poignée de la fenêtre lorsque vous appuyez ou cliquez sur la poignée, ou utilisez le raccourci clavier Touche Meta (Windows, Commande ou Recherche) + Ctrl + Bas.

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

Pour revenir au fenêtrage du bureau, appuyez ou cliquez sur la tuile de l'espace de bureau sur l'écran "Récents".

Optimiser la mise en page de votre application pour un environnement de type ordinateur

La conception d'une expérience sur ordinateur peut différer considérablement de celle sur mobile en raison de l'espace d'écran accru, de la précision de la saisie à la souris et au clavier, et de l'attente d'une productivité élevée.

Jetpack WindowManager fournit une API d'opinion pour aider les développeurs à décider quand afficher une UI de bureau, qui présente généralement une densité d'informations plus élevée, des modèles de navigation différents et des interactions optimisées avec la souris.

lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        windowInfoTracker.windowEngagementInfo(this@DesktopWindowingActivity)
            .collect { windowEngagementInfo ->
                if(windowEngagementInfo.hasEngagementMode(WindowEngagementInfo.EngagementMode.PRECISE_POINTER)){
                    showDesktopOptimizedUI()
                }else {
                    showTouchOptimizedUI()
                }
        }
    }
}

Pour en savoir plus, consultez Concevoir pour les ordinateurs.

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 mode Portrait, les utilisateurs peuvent toujours redimensionner l'application dans une fenêtre en mode 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 le même format.

Figure 4 : L'UI d'une application non redimensionnable s'adapte à la taille de la fenêtre.

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 leurs viseurs : 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, les applications 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, de côté ou à l'envers.

En attendant que les applications soient 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 des hypothèses erronées peuvent entraîner.

Pour en savoir plus sur le mode de compatibilité des applications d'appareil photo, consultez Mode de compatibilité avec les appareils.

Figure 5. Le viseur de l'appareil photo conserve son format lorsque la fenêtre est redimensionnée.

Marges d'en-tête personnalisables

Toutes les applications exécutées en mode fenêtrage du bureau ont 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'option 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 qu'elle corresponde au design 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 cibles, 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 de 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 ou quittez le fenêtrage du bureau.

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 chevauche les boutons du 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 du système. Tout espace restant dans la barre de légende est une zone de sécurité où vous pouvez placer du contenu personnalisé en toute sécurité.

Activez ou désactivez l'apparence des éléments de sous-titres 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 les modes multitâche et multi-instance

Le multitâche est au cœur du fenêtrage du bureau, et 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 du système doit fournir des options (comme un bouton "Nouvelle fenêtre") pour que l'application soit lancée dans plusieurs instances.

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

Remarque : Dans le fenêtrage du bureau et d'autres environnements en mode multifenêtre, 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 glissement

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 d'une 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 glisser pour le glisser-déposer multi-instance permettant aux utilisateurs de faire glisser du contenu vers une autre instance de votre application ou de créer une nouvelle 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 qui contient les données à transférer, ainsi que des indicateurs pour configurer le comportement multi-instance.

Android 15 introduit deux options importantes 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 de la fenêtre (pour plusieurs instances de la même application). Lorsque startDragAndDrop() est appelé avec cette option définie, 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 des 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érer : extrayez les données dans le rappel onDrop.

Optimisations supplémentaires

Personnalisez les lancements d'applications et passez 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 une valeur ajoutée à l'utilisateur. 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 depuis le fenêtrage du bureau.