Descubre los aspectos básicos de Android XR: Parte 2 - Orbitadores y entornos espaciales

1. Antes de comenzar

Qué aprenderás

  • Las experiencias del usuario únicas que hace posibles Android XR
  • Cómo optimizar una app para auriculares de Android XR con la biblioteca de realidad extendida de Jetpack Compose
  • Cómo usar elementos de la IU de la biblioteca de realidad extendida de Jetpack Compose
  • Dónde obtener más información sobre la compilación de apps para Android XR

Qué no es este codelab

Requisitos

Qué compilarás

En este codelab, optimizarás aún más una app con algunas funciones de realidad extendida existentes. Para ello, agregarás elementos de la IU flotantes y personalizarás el entorno virtual que rodea al usuario mientras usa la app.

Punto de partida

Resultado final

2. Prepárate

Obtén el código

  1. El código de este codelab se puede encontrar en el directorio xr-fundamentals dentro del repositorio xr-codelabs de GitHub. Para clonar el repositorio, ejecuta el siguiente comando:
git clone https://github.com/android/xr-codelabs.git
  1. También tienes la opción de descargar el repositorio como archivo ZIP:

Abre el proyecto

  • Después de iniciar Android Studio, importa el directorio xr-fundamentals/part1. El directorio xr-fundamentals/part2 contiene el código de la solución, que puedes consultar en cualquier momento si no logras avanzar o si deseas ver el proyecto completo.

Familiarízate con el código

  • Después de abrir el proyecto en Android Studio, dedica un momento a analizar el código de partida.
  • Si aún no realizaste el primer codelab ni usaste el emulador de Android XR, sigue los pasos que se indican en Ejecuta la app en el emulador de Android XR para ejecutarla.

3. Descubre los conceptos de la realidad extendida: orbitadores

Los orbitadores son elementos de la IU flotantes que están disponibles en el modo de espacio completo que suelen usarse para controlar el contenido dentro de los paneles espaciales o de otras entidades a las que está anclada su órbita. El uso de orbitadores para los controles de contenido le brinda más espacio al contenido, lo que significa que los usuarios pueden acceder rápidamente a las funciones contenidas en los orbitadores mientras el contenido principal permanece visible. Los orbitadores te brindan la versatilidad para integrar componentes de IU existentes (como barras de navegación) o crear otros nuevos.

Además, la API de Orbiter te permite renderizar el contenido de un orbitador donde lo haría normalmente cuando se ejecuta en el modo de espacio principal o en un dispositivo que no es de realidad extendida, y lo divide automáticamente en un orbitador cuando se ejecuta en el modo de espacio completo.

En el modo de espacio principal, este riel de navegación se renderiza dentro del panel principal de la app.

En el modo de espacio completo, el riel de navegación se divide en un orbitador conectado al panel principal.

En este punto, la app contiene un botón en la barra superior para alternar entre el modo de espacio principal y el modo de espacio completo. Este botón es un ejemplo perfecto de un control que puede estar contenido en un orbitador cuando se ejecuta en el modo de espacio completo, ya que mover el control para que orbite el panel principal ayuda a que se destaque y, al mismo tiempo, indica visualmente que el control contraerá el contenido de la app en ese panel cuando se haga clic en él.

Estado actual

Qué implementarás

Para obtener más información sobre las consideraciones de diseño de los orbitadores, consulta IU espacial.

4. Agrega un orbitador

Une el botón de activación del modo de espacio

Para convertir el botón de activación del modo de espacio en un orbitador, une el elemento componible ToggleSpaceModeButton dentro de un elemento componible 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()
}

Ahora, ejecuta la app. Cuando se ejecute en el modo de espacio principal, notarás que no cambió nada. Sin embargo, cuando hagas clic en el botón de activación y la app entre en el modo de espacio completo, notarás que el botón ya no se encuentra en la barra superior de la app, sino en el borde superior derecho del panel espacial principal.

El orbitador está anclado al panel espacial principal porque esa es la entidad espacial superior más cercana en el árbol de la IU. La posición exacta del orbitador en relación con el panel espacial principal se determina con los parámetros position, alignment y offset. Intenta modificar estos parámetros para ver el rango de comportamientos que admiten.

Modo de espacio principal

Modo de espacio completo

5. Descubre los conceptos de la realidad extendida: entornos espaciales

Usar un Orbiter para personalizar la posición en el espacio 3D de los elementos de la IU es una excelente manera de mejorar la experiencia del usuario en dispositivos de realidad extendida. Puedes personalizar el entorno espacial en el que se encuentran los usuarios cuando usan tu app para mejorar aún más la experiencia.

Los entornos espaciales pueden incorporar recursos de profundidad, textura y geometría 3D para crear una experiencia visual envolvente y rica. Esto se logra usando una imagen de skybox esférica (en formato EXR) para proporcionar un fondo panorámico distante o un recurso de geometría (en formato glTF) para proporcionar elementos de primer plano y plano medio que se pueden combinar en un skybox. Por ejemplo, una app de transmisión de video por Internet podría usar un skybox nocturno con un glTF de un autocine con una pantalla de proyección y autos. Cuando crees recursos para configurar el entorno espacial de tus usuarios, asegúrate de que estos alcancen una resolución de alta calidad y, al mismo tiempo, mantengan un tamaño de archivo razonable. Consulta Optimiza los recursos del entorno para obtener más información.

Además, se puede controlar la opacidad del entorno espacial. Esto permite que una transmisión de video por Internet del mundo real pase y se combine con el entorno virtual, lo que puede ayudar a los usuarios a mantener el rumbo.

Una persona de pie en un entorno espacial rocoso con un gran panel de IU en medio del campo.

En el siguiente paso, agregarás un recurso de geometría a tu app y crearás un menú para permitir que el usuario elija su entorno.

Para obtener todos los detalles sobre el diseño y la implementación de entornos espaciales, consulta Entornos espaciales y Agrega entornos espaciales a tu app.

6. Permite que los usuarios cambien el entorno espacial

Cómo las apps controlan el entorno espacial

Antes de comenzar, es bueno comprender cómo las apps pueden controlar exactamente el entorno espacial.

A diferencia del contenido de los paneles, las apps no controlan directamente el entorno. En su lugar, pueden interactuar con la sesión de SceneCore para proporcionar una preferencia por el entorno que desean que use el sistema. Esta preferencia se representa con una SpatialEnvironmentPreference, que consiste en una imagen EXR de skybox o un gLTF de geometría. Lo que sucede cuando tu app proporciona una preferencia depende de las capacidades de la app cuando establece la preferencia. Si tu app tiene la capacidad de cambiar el entorno, el sistema la usará de inmediato. De lo contrario, la preferencia se aplicará cuando tu app tenga esa capacidad.

Por ejemplo, las apps suelen no tener la capacidad de cambiar el entorno mientras se ejecutan en el modo de espacio principal, pero sí lo hacen cuando se ejecutan en el modo de espacio completo. Por lo tanto, si permites que un usuario establezca una preferencia de entorno mientras está en el modo de espacio principal, esa preferencia generalmente no tendrá efecto hasta que la app se ejecute en el modo de espacio completo.

Agrega una dependencia en la biblioteca de XR SceneCore

Para comenzar a modificar el entorno espacial, agrega una dependencia en la biblioteca XR SceneCore, que usarás para cargar los recursos del entorno y establecer las preferencias del entorno. También deberás agregar una dependencia en el artefacto kotlinx-coroutines-guava, ya que algunas de las APIs para cargar recursos usan el tipo de datos 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)
    ...
}

Agrega un recurso de entorno a tu proyecto

Para especificar una preferencia de entorno única, necesitarás un skybox o un recurso de geometría. En este codelab, solo usarás el recurso de geometría green_hills_ktx2_mipmap.glb, que puedes encontrar en la carpeta part2 que contiene el código de la solución o en GitHub.

  1. Haz clic con el botón derecho en el módulo de la app en la ventana Project de Android Studio. Luego, selecciona New > Folder > Assets Folder y, luego, haz clic en Finish para crear la carpeta.
  2. Agrega el archivo GLB a la carpeta app/src/main/assets que acabas de crear.

Modela las opciones de entorno

Para simplificar la interacción entre el código de la IU y las APIs del sistema, puedes crear una clase de datos de Kotlin para modelar cada opción de entorno.

  1. Haz clic con el botón derecho en el paquete com.example.android.xrfundamentals de la ventana Project y selecciona New > Package. Ingresa com.example.android.xrfundamentals.environment como el nombre del paquete.
  2. Haz clic con el botón derecho en el paquete y selecciona New > Kotlin Class/File. Ingresa EnvironmentOption como nombre y haz clic en el tipo Data class.
  3. Agrega el siguiente código en el archivo que acabas de crear:

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

Agrega un ayudante para crear recursos de carga y mostrar SpatialEnvironmentPreference

A continuación, puedes agregar un método de ayuda a la clase de datos para facilitar la conversión de una EnvironmentOption en la SpatialEnvrionmentPreference correspondiente.

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

Ten en cuenta lo siguiente:

  • Si el skybox y la geometría son nulos, se muestra un valor nulo para indicar que se debe usar la preferencia de entorno del sistema predeterminada. Consulta setSpatialEnvironmentPreference para obtener más información.
  • Los recursos skybox y geometry se crean de forma asíncrona porque estos recursos suelen ser bastante grandes y tardan en leerse en la memoria. En una app de producción, te recomendamos que almacenes en caché estos recursos en la memoria si cambias de entorno con frecuencia.

Implementa la IU de selección de entornos

Para implementar la IU, agregarás un segundo orbitador que rote entre las opciones de entorno cuando se haga clic en él.

Agrega el orbitador

  1. Haz clic con el botón derecho en el módulo app de la ventana Project y selecciona New > Vector Asset. Haz clic en el campo Clip art y busca y selecciona el recurso landscape (de la familia de íconos Filled). Luego, haz clic en OK y, luego, en Next para crear el recurso.
  2. Haz clic con el botón derecho en el paquete com.example.android.xrfundamentals.ui.component y selecciona New > Kotlin Class/File. Ingresa EnvironmentSelectionOrbiter como nombre y haz clic en el tipo File.
  3. En el archivo que acabas de crear, agrega la siguiente implementación del elemento componible 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. Por último, agrega el EnvironmentSelectionOrbiter dentro del panel espacial 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() }
        )
    }
    ...
}

Cambia el entorno cuando se hace clic en el orbitador

Para que todo funcione, hay un último paso, que es llamar a setSpatialEnvironmentPreference en el controlador de clics EnvironmentSelectionOrbiter.

  1. Configura una variable para hacer un seguimiento de la opción de entorno actual (fuera del Subspace para que se mantenga el estado cuando se cambie entre el modo de espacio principal y el modo de espacio completo). Además, crea variables para la sesión de realidad extendida actual y un alcance de corrutina para llamar al ayudante 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. Implementa la devolución de llamada onClick para rotar por las opciones de entorno.

XRFundamentalsApp.kt

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

Ejecuta la app una vez más y comprueba que puedes cambiar entre los entornos Green Hills y predeterminado.

b0e9571ef5f5597b.gif

7. Felicitaciones

Para continuar aprendiendo a aprovechar al máximo la realidad extendida, consulta los siguientes recursos y ejercicios:

Lecturas adicionales

Desafíos

  • Busca o crea recursos de entorno adicionales y agrégalos como opciones.
  • Modifica el controlador de entorno y la IU para permitir que el usuario establezca sus preferencias de transferencia con la API de setPassthroughOpacityPreference. Ten en cuenta que el control de transferencia está restringido por una capacidad diferente que la de cambiar los recursos del entorno.

Documentos de referencia