Principes de base d'Android XR : Partie 2 – Orbiteurs et environnements spatiaux

1. Avant de commencer

Points abordés

  • Expériences utilisateur uniques rendues possibles par Android XR.
  • Optimiser une application pour un casque XR Android à l'aide de la bibliothèque Jetpack Compose XR
  • Utiliser des éléments d'interface utilisateur de la bibliothèque Jetpack Compose XR
  • Ressources permettant d'en savoir plus sur la création d'applications pour Android XR.

Ce que cet atelier n'est pas

Ce dont vous avez besoin

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez optimiser une application avec certaines fonctionnalités XR existantes en ajoutant des éléments d'interface utilisateur flottants et en personnalisant l'environnement virtuel qui entoure l'utilisateur lorsqu'il utilise l'application.

Point de départ

Résultat final

2. Configuration

Obtenir le code

  1. Le code pour cet atelier de programmation se trouve dans le répertoire xr-fundamentals au sein du dépôt GitHub xr-codelabs. Pour cloner le dépôt, exécutez la commande suivante :
git clone https://github.com/android/xr-codelabs.git
  1. Vous pouvez aussi télécharger le dépôt sous la forme d'un fichier ZIP :

Télécharger le fichier ZIP

Ouvrir le projet

  • Après avoir démarré Android Studio, importez le répertoire xr-fundamentals/part1. Le répertoire xr-fundamentals/part2 contient le code de solution, que vous pouvez consulter à tout moment en cas de difficulté ou pour avoir un aperçu du projet dans son ensemble.

Vous familiariser avec le code

  • Après avoir ouvert le projet dans Android Studio, prenez le temps d'examiner le code de démarrage.
  • Si vous n'avez pas encore suivi le premier atelier de programmation ou utilisé l'émulateur Android XR, suivez la procédure décrite dans la section Exécuter l'application dans l'émulateur Android XR pour exécuter l'application.

3. Apprendre les concepts de la réalité étendue: orbiteurs

Les orbiteurs sont des éléments d'interface utilisateur flottants disponibles en mode Espace complet. Ils sont généralement utilisés pour contrôler le contenu des panneaux spatiaux ou d'autres entités auxquelles leur orbite est ancrée. L'utilisation d'orbiteurs pour les commandes de contenu permet de gagner de l'espace. Les utilisateurs peuvent ainsi accéder rapidement aux fonctionnalités contenues dans les orbiteurs, tandis que le contenu principal reste visible. Les orbiteurs vous permettent d'intégrer des composants d'interface utilisateur existants (comme des barres de navigation) ou d'en créer.

De plus, l'API Orbiter vous permet d'afficher le contenu d'un orbiteur là où il devrait normalement s'afficher en mode d'affichage restreint ou sur un appareil non XR, et de le diviser automatiquement en orbiteur lorsqu'il s'exécute en mode d'affichage complet.

En mode d'affichage restreint, ce rail de navigation s'affiche dans le panneau principal de l'application.

En mode d'affichage complet, le rail de navigation est divisé en un élément orbiteur associé au panneau principal.

À ce stade, l'application contient un bouton dans la barre d'application supérieure permettant de basculer entre le mode d'affichage restreint et le mode d'affichage complet. Ce bouton est un exemple parfait de commande pouvant être contenue dans un élément orbiteur en mode d'affichage complet. En effet, le fait de placer la commande en orbite autour du panneau principal permet de la mettre en valeur tout en indiquant visuellement qu'elle réduira le contenu de l'application dans ce panneau lorsque l'utilisateur cliquera dessus.

État actuel

Ce que vous allez implémenter

Pour en savoir plus sur les considérations de conception des orbiteurs, consultez la section UI spatiale.

4. Ajouter un orbiteur

Encapsuler le bouton d'activation du mode d'affichage

Pour transformer le bouton d'activation du mode d'affichage en orbiteur, encapsulez le composable ToggleSpaceModeButton dans un composable Orbiter.

ui/component/XRFundamentalsTopAppBar .kt

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import androidx.xr.compose.spatial.EdgeOffset
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape

... 

Orbiter(
    position = OrbiterEdge.Top,
    alignment = Alignment.End,
    offset = EdgeOffset.inner(16.dp),
    shape = SpatialRoundedCornerShape(
        CornerSize(percent = 100)
    )
) {
    ToggleSpaceModeButton()
}

Exécutez maintenant l'application : en mode d'affichage restreint, vous remarquerez que rien n'a changé. Toutefois, lorsque vous cliquez sur l'interrupteur et que l'application passe en mode d'affichage complet, vous remarquerez que le bouton n'est plus situé dans la barre d'application supérieure, mais en haut à droite du panneau spatial principal.

L'orbiteur est ancré au panneau spatial principal, car il s'agit de l'entité spatiale parente la plus proche dans l'arborescence de l'interface utilisateur. La position exacte de l'orbiteur par rapport au panneau spatial principal est déterminée par les paramètres position, alignment et offset. Essayez de modifier ces paramètres pour voir la gamme de comportements qu'ils acceptent.

Mode d'affichage restreint

Mode d'affichage complet

5. Apprendre les concepts de la réalité étendue : environnements spatiaux

Utiliser un Orbiter pour personnaliser la position des éléments d'interface utilisateur dans l'espace 3D est un excellent moyen d'améliorer l'expérience utilisateur sur les appareils de réalité étendue. Vous pouvez améliorer encore l'expérience en personnalisant l'environnement spatial dans lequel se trouvent les utilisateurs lorsqu'ils utilisent votre application.

Les environnements spatiaux peuvent intégrer des éléments de profondeur, de texture et de géométrie 3D pour créer une expérience visuelle immersive riche. Pour ce faire, utilisez une image skybox sphérique (au format EXR) pour fournir un arrière-plan panoramique éloigné et/ou un élément de géométrie (au format glTF) pour fournir des éléments de premier et de deuxième plans pouvant se fondre dans une skybox. Par exemple, une application de streaming vidéo peut utiliser une skybox de nuit avec un glTF d'un cinéma drive-in avec un écran de projection et des voitures. Lorsque vous créez des éléments pour définir l'environnement spatial de vos utilisateurs, assurez-vous qu'ils offrent une résolution de haute qualité tout en conservant une taille de fichier raisonnable. Pour en savoir plus, consultez Optimiser les éléments d'environnement.

De plus, l'opacité de l'environnement spatial peut être contrôlée. Cela permet à un flux vidéo du monde réel de passer à travers l'environnement virtuel et de s'y fondre, ce qui peut aider les utilisateurs à se repérer.

Une personne debout dans un environnement spatial rocheux avec un grand panneau d'interface utilisateur au milieu du champ.

À l'étape suivante, vous allez ajouter un élément géométrique à votre application et créer un menu pour permettre à l'utilisateur de choisir son environnement.

Pour en savoir plus sur la conception et l'implémentation des environnements spatiaux, consultez Environnements spatiaux et Ajouter des environnements spatiaux à votre application.

6. Autoriser les utilisateurs à modifier l'environnement spatial

Comment les applications contrôlent l'environnement spatial

Avant de commencer, il est important de comprendre comment les applications peuvent contrôler l'environnement spatial.

Contrairement au contenu des panneaux, les applications ne contrôlent pas directement l'environnement. Au lieu de cela, ils peuvent interagir avec la session SceneCore pour indiquer l'environnement qu'ils souhaitent que le système utilise. Cette préférence est représentée par un SpatialEnvironmentPreference, qui se compose d'une image EXR de skybox et/ou d'un fichier gLTF de géométrie. Ce qui se passe lorsque votre application fournit une préférence dépend des capacités de votre application lorsqu'elle définit la préférence. Si votre application a la capacité de changer d'environnement, le système l'utilisera immédiatement. Si ce n'est pas le cas, la préférence sera appliquée lorsque votre application aura cette capacité.

Par exemple, les applications ne peuvent généralement pas changer d'environnement lorsqu'elles s'exécutent en mode d'affichage restreint, mais elles le peuvent généralement en mode d'affichage complet. Par conséquent, si vous autorisez un utilisateur à définir une préférence d'environnement en mode d'affichage restreint, cette préférence ne s'appliquera généralement que lorsque votre application s'exécutera en mode d'affichage complet.

Ajouter une dépendance à la bibliothèque XR SceneCore

Pour commencer à modifier l'environnement spatial, ajoutez une dépendance à la bibliothèque XR SceneCore, que vous utiliserez pour charger les éléments de l'environnement et définir les préférences de l'environnement. Vous devrez également ajouter une dépendance à l'artefact kotlinx-coroutines-guava, car certaines API de chargement d'éléments utilisent le type de données ListenableFuture.

libs.version.toml

[versions]
...
xrSceneCore = "1.0.0-alpha04"
kotlinxCoroutinesGuava = "1.10.2"

[libraries]
...
androidx-xr-scenecore = { group = "androidx.xr.scenecore", name = "scenecore", version.ref = "xrSceneCore"}
jetbrains-kotlinx-coroutines-guava = {group = "org.jetbrains.kotlinx", name="kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava"}

app/build.gradle.kts

dependencies {
    ...
    implementation(libs.androidx.xr.scenecore)
    implementation(libs.jetbrains.kotlinx.coroutines.guava)
    ...
}

Ajouter un élément d'environnement à votre projet

Pour spécifier une préférence d'environnement unique, vous aurez besoin d'une skybox et/ou d'un élément de géométrie. Pour cet atelier de programmation, vous n'utiliserez que l'élément géométrique green_hills_ktx2_mipmap.glb, que vous trouverez dans le dossier part2 contenant le code de solution ou sur GitHub.

  1. Effectuez un clic droit sur le module d'application dans la fenêtre "Project" (Projet) d'Android Studio. Sélectionnez ensuite Nouveau > Dossier > Dossier d'éléments, puis cliquez sur Terminer pour créer le dossier.
  2. Ajoutez le fichier GLB dans le dossier app/src/main/assets que vous venez de créer.

Modéliser les options d'environnement

Pour simplifier l'interaction entre le code de l'interface utilisateur et les API système, vous pouvez créer une classe de données Kotlin pour modéliser chaque option d'environnement.

  1. Effectuez un clic droit sur le package com.example.android.xrfundamentals dans la fenêtre du projet, puis sélectionnez New > Package (Nouveau > Package). Saisissez com.example.android.xrfundamentals.environment comme nom de package.
  2. Effectuez un clic droit sur le package , puis sélectionnez New > Kotlin Class/File (Nouveau > Classe/Fichier Kotlin). Saisissez EnvironmentOption comme nom, puis cliquez sur le type Classe de données.
  3. Ajoutez le code suivant dans le fichier que vous venez de créer :

EnvironmentOption.kt

data class EnvironmentOption(val name: String, val skyboxPath: String?, val geometryPath: String?)

val DEFAULT_ENVIRONMENT = EnvironmentOption("Default", null, null)

val ENVIRONMENT_OPTIONS = listOf(
    DEFAULT_ENVIRONMENT,
    EnvironmentOption("Green Hills", null, "green_hills_ktx2_mipmap.glb")
)

Ajout d'un assistant pour créer des éléments de chargement et renvoyer SpatialEnvironmentPreference

Vous pouvez ensuite ajouter une méthode d'assistance à la classe de données pour faciliter la conversion d'un EnvironmentOption en SpatialEnvrionmentPreference correspondant.

EnvironmentOption.kt

import androidx.xr.runtime.Session
import androidx.xr.scenecore.ExrImage
import androidx.xr.scenecore.GltfModel
import androidx.xr.scenecore.SpatialEnvironment
import kotlinx.coroutines.guava.await

...

data class EnvironmentOption(val name: String, val skyboxPath: String?, val geometryPath: String?) {
    suspend fun toSpatialEnvironmentPreference(session: Session): SpatialEnvironmentPreference? {
        if (skyboxPath == null && geometryPath == null) {
            return null
        } else {
            val skybox = skyboxPath?.let {
                ExrImage.create(session, it).await()
            }

            val geometry = geometryPath?.let {
                GltfModel.create(session, it).await()
            }

            return SpatialEnvironmentPreference(skybox, geometry)
        }
    }
}

Voici quelques points à noter :

  • Si la skybox et la géométrie sont nulles, la valeur "null" est renvoyée pour indiquer que la préférence d'environnement système par défaut doit être utilisée. Pour en savoir plus, consultez setSpatialEnvironmentPreference.
  • Les ressources skybox et geometry sont créées de manière asynchrone, car ces éléments peuvent souvent être assez volumineux et prendre du temps à être lus en mémoire. Dans une application de production, vous pouvez envisager de mettre en cache ces éléments en mémoire si vous changez souvent d'environnement.

Implémenter l'interface utilisateur de sélection de l'environnement

Pour implémenter l'interface utilisateur, vous allez ajouter un deuxième orbiteur qui fait défiler les options d'environnement lorsque l'utilisateur clique dessus.

Ajouter l'orbiteur

  1. Effectuez un clic droit sur le module app dans la fenêtre du projet, puis sélectionnez New > Vector Asset (Nouveau > Élément vectoriel). Cliquez sur le champ Image clipart, puis recherchez et sélectionnez l'élément landscape (dans la famille d'icônes Rempli). Cliquez ensuite sur OK, puis sur Suivant pour créer l'élément.
  2. Effectuez un clic droit sur le package com.example.android.xrfundamentals.ui.component, puis sélectionnez New > Kotlin Class/File (Nouveau > Classe/Fichier Kotlin). Saisissez EnvironmentSelectionOrbiter comme nom, puis cliquez sur le type File (Fichier).
  3. Dans le fichier que vous venez de créer, ajoutez l'implémentation suivante du composable EnvironmentSelectionOrbiter.

EnvironmentSelectionOrbiter.kt

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.xr.compose.spatial.EdgeOffset
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape
import com.example.android.xrfundamentals.R

@Composable
fun EnvironmentSelectionOrbiter(
    modifier: Modifier = Modifier,
    onClick: () -> Unit = {},
) {
    Orbiter(
        position = OrbiterEdge.Top,
        alignment = Alignment.Start,
        offset = EdgeOffset.inner(16.dp),
        shape = SpatialRoundedCornerShape(
            CornerSize(100)
        )
    ) {
        FilledTonalIconButton(
            modifier = modifier,
            onClick = onClick,
        ) {
            Icon(painterResource(R.drawable.baseline_landscape_24), "Show environment selection dialog")
        }
    }
}
  1. Enfin, ajoutez EnvironmentSelectionOrbiter dans le panneau spatial principal.

XRFundamentalsApp.kt

import androidx.xr.compose.platform.LocalSpatialCapabilities
import com.example.android.xrfundamentals.ui.component.EnvironmentSelectionOrbiter

...

SpatialPanel(...) {

    // Only show the environment selection orbiter if the app is actually able to
    // change the environment
    if (LocalSpatialCapabilities.current.isAppEnvironmentEnabled) {
        EnvironmentSelectionOrbiter(
            onClick = { TODO() }
        )
    }
    ...
}

Modifier l'environnement lorsque l'utilisateur clique sur l'orbiteur

Pour que tout fonctionne, il ne reste qu'une dernière étape : appeler setSpatialEnvironmentPreference dans le gestionnaire de clics EnvironmentSelectionOrbiter.

  1. Configurez une variable pour suivre l'option d'environnement actuelle (en dehors de Subspace afin que l'état soit conservé lorsque vous passez du mode d'affichage restreint au mode d'affichage complet). Créez également des variables pour la session XR actuelle et un champ d'application de coroutine pour appeler l'assistant toSpatialEnvironmentPreference.

XRFundamentalsApp.kt

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.xr.compose.platform.LocalSession

... 

var currentEnvironmentOptionIndex by remember { mutableStateOf(0) }

Subspace {
    val session = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    ...
}
  1. Implémentez le rappel onClick pour faire défiler les options d'environnement.

XRFundamentalsApp.kt

EnvironmentSelectionOrbiter(
    onClick = {
        scope.launch {
            currentEnvironmentOptionIndex =
                (currentEnvironmentOptionIndex + 1) % ENVIRONMENT_OPTIONS.size
            session.scene.spatialEnvironment.setSpatialEnvironmentPreference(
                ENVIRONMENT_OPTIONS[currentEnvironmentOptionIndex].toSpatialEnvironmentPreference(
                    session
                )
            )
        }
    }
)

Exécutez à nouveau votre application et vérifiez que vous pouvez passer de l'environnement Green Hills à l'environnement par défaut.

b0e9571ef5f5597b.gif

7. Félicitations

Pour continuer à apprendre comment exploiter Android XR au mieux, consultez les ressources et exercices ci-dessous :

Complément d'informations

Défis

  • Recherchez ou créez des éléments d'environnement supplémentaires, puis ajoutez-les en tant qu'options.
  • Modifiez le contrôleur d'environnement et l'interface utilisateur pour permettre à l'utilisateur de définir ses préférences de transfert à l'aide de l'API setPassthroughOpacityPreference. Notez que le contrôle du transfert est soumis à une capacité différente de celle utilisée pour modifier les éléments de l'environnement.

Documents de référence