Descubre los aspectos básicos de Android XR: Parte 1 - Espacios y paneles espaciales

1. Antes de comenzar

Qué aprenderás

  • Las experiencias del usuario únicas que hace posibles el factor de forma de realidad extendida
  • Los aspectos básicos de cómo adaptar tu app para aprovechar al máximo su ejecución en un visor de realidad extendida de Android XR (por ejemplo, usando los elementos componibles que proporciona la biblioteca de Jetpack Compose para realidad extendida)
  • Cómo usar los elementos de la IU proporcionados por la biblioteca de realidad extendida de 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, mejorarás una app básica con una única pantalla para ofrecer una experiencia del usuario inmersiva a través de Android XR.

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 proyecto. Elige solamente el directorio xr-fundamentals/start. El directorio xr-fundamentals/part1 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.

3. Descubre los conceptos de realidad extendida: Espacios y paneles espaciales

En este codelab, aprenderás dos conceptos de Android XR: los espacios y los paneles espaciales. También aprenderás a aplicar estos conceptos a apps que se ejecuten en un dispositivo Android XR.

Espacios

En dispositivos Android XR, las apps se ejecutan en uno de dos espacios: el espacio principal o el espacio completo.

Espacio principal

d779257a53898d36.jpeg

En el espacio principal, varias apps se ejecutan una al lado de la otra para que los usuarios puedan realizar varias tareas a la vez en las apps. Las apps para Android se pueden ejecutar en el espacio principal sin modificación.

Espacio completo

c572cdee69669a23.jpeg

Para obtener más información sobre estos espacios, consulta ​​ Espacio principal y espacio completo.

Paneles espaciales

Los paneles espaciales son elementos contenedores que constituyen los componentes básicos de las apps para Android XR.

Cuando se ejecuta en el Espacio principal, la app estará contenida dentro de un único panel para lograr una experiencia similar al modo de ventanas de escritorio en un dispositivo Android de pantalla grande.

Cuando se ejecuta en el espacio completo, se puede dividir el contenido de la app en uno o más paneles para ofrecer una experiencia más inmersiva.

Para obtener más información sobre los paneles, consulta Paneles espaciales.

4. Ejecuta la app en el emulador de Android XR

Antes de comenzar a mejorar la app para Android XR, puedes ejecutarla en el emulador de Android XR para ver qué aspecto tiene en el espacio principal.

Instala la imagen del sistema de Android XR

  1. Primero, abre SDK Manager en Android Studio y selecciona la pestaña SDK Platforms si aún no está seleccionada. En la esquina inferior derecha de la ventana de SDK Manager, asegúrate de que esté marcada la casilla junto a Show package details.
  2. En la sección Android 14, instala la imagen del emulador Android XR ARM 64 v8a o Android XR Intel x86_64. Las imágenes solo se pueden ejecutar en máquinas que tienen su misma arquitectura (x86/ARM).

Crea un dispositivo virtual Android XR

  1. Después de abrir el Administrador de dispositivos, selecciona XR en la columna Category en la parte izquierda de la ventana. Luego, selecciona el perfil de hardware XR Device de la lista y haz clic en Next.

7a5f6b9c1766d837.png

  1. En la siguiente página, selecciona la imagen del sistema que instalaste anteriormente. Haz clic en Next y selecciona las opciones avanzadas que desees antes de crear el AVD haciendo clic en Finish.
  2. Ejecuta la app en el AVD que acabas de crear.

51a6b3eb02916d74.png

5. Configura dependencias

Antes de poder comenzar a agregar funciones específicas de realidad extendida a tu app, tendrás que agregar una dependencia en Jetpack Compose para la biblioteca de realidad extendida, androidx.xr.compose:compose, que contiene todos los elementos componibles que necesitas para compilar una experiencia diferenciada de Android XR para tu app.

libs.version.toml

[versions]
...
xrCompose = "1.0.0-alpha13"

[libraries]
...
androidx-xr-compose = { group = "androidx.xr.compose", name = "compose", version.ref = "xrCompose" }

build.gradle.kts (módulo :app)

dependencies {
    ...
    implementation(libs.androidx.xr.compose)
    ...
}

Después de actualizar estos archivos, asegúrate de hacer una sincronización de Gradle para que las dependencias se descarguen en tu proyecto.

6. Abre el espacio completo

Para usar las funciones de realidad extendida como paneles, una app debe ejecutarse en el espacio completo. Una app puede abrir el espacio completo de dos formas:

  • De forma programática, como en respuesta a la interacción del usuario dentro de tu app
  • Inmediatamente después del inicio, agregando una directiva al manifiesto de la app

Cómo abrir el espacio completo de forma programática

Para abrir el espacio completo de forma programática, puedes ofrecer indicaciones visuales en tu IU para que el usuario pueda controlar en qué modo quiere usar tu app. Además, puedes abrir el espacio completo cuando corresponda según el contexto de uso de la app. Por ejemplo, se puede abrir el espacio completo cuando se comienza a ver contenido de video y salir de él cuando finaliza la reproducción.

Para simplificar las cosas, este proceso se puede lograr agregando un botón en la barra superior de la aplicación para activar o desactivar el espacio.

  1. Crea un nuevo archivo ToggleSpaceButton.kt en el paquete com.example.android.xrfundamentals.ui.component y agrega los siguientes elementos componibles:

ToggleSpaceButton.kt

package com.example.android.xrfundamentals.ui.component

import androidx.annotation.DrawableRes
import androidx.compose.material3.Icon
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.xr.compose.platform.LocalSpatialCapabilities
import androidx.xr.compose.platform.LocalSpatialConfiguration
import com.example.android.xrfundamentals.R
import com.example.android.xrfundamentals.ui.theme.XRFundamentalsTheme

@Composable
fun ToggleSpaceButton(
    onHomeSpaceRequested: () -> Unit,
    onFullSpaceRequested: () -> Unit,
    modifier: Modifier = Modifier
) {
    if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
        ToggleSpaceButton(
            modifier = modifier,
            contentDescription = "Request Home Space",
            iconResource = R.drawable.ic_home_space,
            onClick = { onHomeSpaceRequested() }
        )
    } else {
        ToggleSpaceButton(
            modifier = modifier,
            contentDescription = "Request Full Space",
            iconResource = R.drawable.ic_full_space,
            onClick = { onFullSpaceRequested() }
        )
    }
}

@Composable
fun ToggleSpaceButton(
    contentDescription: String,
    @DrawableRes iconResource: Int,
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    FilledTonalIconButton(
        modifier = modifier,
        onClick = onClick
    ) {
        Icon(
            painterResource(iconResource),
            contentDescription
        )
    }
}
  1. Agrega el botón como una acción en la TopAppBar cuando la app se esté ejecutando en un dispositivo de realidad extendida.

XRFundamentalsTopAppBar.kt

import androidx.xr.compose.platform.LocalSpatialConfiguration

...

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun XRFundamentalsTopAppBar(
    onHomeSpaceRequested: () -> Unit,
    onFullSpaceRequested: () -> Unit,
) {
    TopAppBar(
        title = { Text(stringResource(R.string.app_name)) },
        actions = {
            // Only show the space toggle if the device supports spatial UI
            if (LocalSpatialConfiguration.current.hasXrSpatialFeature) {
                ToggleSpaceButton(onHomeSpaceRequested, onFullSpaceRequested)
            }
        }
    )
}

Y eleva las interacciones de onHomeSpaceRequested y onFullSpaceRequested a través de XRFundamentalsApp y hacia MainActivity:

XRFundamentalsApp.kt

import androidx.xr.compose.spatial.Subspace

...

@Composable
fun XRFundamentalsApp(
    ...
    onHomeSpaceRequested: () -> Unit,
    onFullSpaceRequested: () -> Unit,
) {

Desde MainActivity, podemos llamar a los métodos de extensión para solicitar el espacio principal y el espacio completo.

MainActivity.kt

...
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            XRFundamentalsTheme {
                XRFundamentalsApp(
                    onHomeSpaceRequested = {
                        lifecycleScope.launch {
                            requestHomeSpace()
                        }
                    },
                    onFullSpaceRequested = {
                        lifecycleScope.launch {
                            requestFullSpace()
                        }
                    },
                )
            }
        }
    }
}

Ahora, ejecuta la app.

La app ejecutándose en el espacio principal cuando se inicia. Presiona el botón de la parte superior derecha del panel para cambiar al espacio completo.

La app que se ejecuta en el espacio completo. Ten en cuenta que ya no está la IU del sistema para minimizar/cerrar la app. Presiona el botón de la parte superior derecha del panel para volver al espacio principal.

Estos fragmentos incluyen dos APIs para observar:

  • LocalSpatialConfiguration es un local de composición que ofrece acceso a la configuración espacial actual de la app. Más allá de los métodos para solicitar cambiar de espacio, esto incluye otra información como el tamaño del volumen que contiene la app.
  • LocalSpatialCapabilities es un local de composición que se puede usar para determinar qué capacidades espaciales están disponibles para que use una app. Además del espacio (principal o completo), esto incluye capacidades como audio espacial y compatibilidad con contenido 3D.

Cómo abrir el espacio completo al inicio

Para indicar al SO que inicie una actividad en el espacio completo, puedes incluir un elemento <property> con los siguientes atributos dentro del elemento <activity> correspondiente. Esto se recomienda únicamente si no es probable que los usuarios quieran usar otra app al mismo tiempo en que están usando la tuya.

AndroidManifest.xml

<activity
    android:name=".MainActivity" 
    ... >
    <property
        android:name="android.window.PROPERTY_XR_ACTIVITY_START_MODE"
        android:value="XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED" />
</activity>

Ahora, cuando se inicia la app, el usuario es llevado inmediatamente al espacio completo.

5eec781e78280eda.gif

Antes de continuar, quita el elemento <property> mencionado antes del manifiesto para que la app use el comportamiento predeterminado de abrirse en el espacio principal.

7. Divide la IU en varios paneles

Ahora que tu app puede abrir el espacio completo y salir de él, es momento de aprovecharlo mejor. Una fantástica forma de hacerlo es dividir el contenido de tu app en varios paneles para llenar el espacio y (opcionalmente) permitir que los usuarios muevan los paneles y les cambien el tamaño como lo deseen.

Incorpora tu app a un subespacio

Para comenzar, agrega un elemento Subspace componible después del elemento Scaffold componible en el elemento XRFundamentalsApp componible. Los subespacios son una partición de espacio 3D dentro de tu app, en donde puedes compilar diseños 3D (p. ej., agregando paneles espaciales), colocar modelos 3D y agregar profundidad a contenido que, de otra forma, es 2D.

Cuando se ejecuta en un dispositivo que no es de realidad extendida, el contenido del elemento Subspace componible nunca entra en la composición. Cuando se ejecuta en un dispositivo de realidad extendida, el contenido solo entra en la composición cuando la app se está ejecutando en el espacio completo.

XRFundamentalsApp.kt

import androidx.xr.compose.spatial.Subspace

...

HelloAndroidXRTheme {
    Scaffold(...)
    Subspace {
    }
}

Ahora, ejecuta la app:

8969b8fd2a79a669.gif

Cuando tu app incluya un elemento Subspace componible, solo se mostrará su contenido. Esto significa que, cuando hagas clic en el botón para abrir el espacio completo, ya no se mostrará nada. Para corregir esto, debes agregar dos paneles espaciales en los siguientes pasos: uno que contenga el contenido principal y otro que contenga el contenido secundario.

Agrega un panel para el contenido principal

Para mostrar el contenido principal en el espacio completo, agrega un SpatialPanel dentro del elemento componible Subspace.

Debido a que este es el panel principal de la app, puedes incluir el Scaffold dentro de él para mantener presentes los controles dentro de la barra superior de la aplicación.

XRFundamentalsApp.kt

import androidx.xr.compose.subspace.SpatialPanel
import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.height
import androidx.xr.compose.subspace.layout.width

...

Subspace {
    SpatialPanel(
        modifier = SubspaceModifier
            .width(1024.dp)
            .height(800.dp)
    ) {
        Scaffold(
            topBar = { XRFundamentalsTopAppBar() }
        ) { innerPadding ->
            Box(Modifier.padding(innerPadding)) {
                PrimaryCard(
                    modifier = Modifier
                        .padding(16.dp)
                        .verticalScroll(rememberScrollState())
                )
            }
        }
    }
}

De forma predeterminada, el tamaño de un SpatialPanel se determina en función del tamaño de su contenido, pero también se puede determinar proporcionando un parámetro SubspaceModifier. Los modificadores de subespacios son similares a los modificadores y se usan para cambiar componentes del diseño espacial como los paneles.

Como el contenido de este panel espacial no especifica su propio tamaño, se debe proporcionar un SubspaceModifier para que el panel tenga un ancho y una altura.

c3db4b5ec6f3b39e.gif

Agrega un panel para el contenido secundario

Ahora que estás ejecutando la app en el espacio completo y usando un panel para mostrar el contenido principal, es momento de mover el contenido secundario a su propio panel. Observa el uso de una Surface dentro del panel espacial. Sin ella, no habría un segundo plano para las tarjetas secundarias, ya que los paneles espaciales son transparentes (el elemento Scaffold componible controló este aspecto en el paso anterior).

XRFundamentalsApp.kt

import androidx.compose.material3.Surface

...

Subspace {
    SpatialPanel() { ... }
    SpatialPanel(
        modifier = SubspaceModifier
            .width(340.dp)
            .height(800.dp)
    ) {
        Surface {
            SecondaryCardList(
                modifier = Modifier
                    .padding(16.dp)
                    .verticalScroll(rememberScrollState())
            )
        }
    }
}

Ahora, vuelve a ejecutar la app. A simple vista, puede parecer que el segundo panel no se ve, pero en realidad sí (solo que está oculto detrás del panel principal).

34936c6d0d82adb8.gif

Diseña los paneles en una fila

Al igual que con el contenido 2D, el uso de filas y columnas es útil para organizar elementos componibles uno al lado del otro sin superponerse. Al trabajar con componentes espaciales como paneles, puedes usar los elementos componibles SpatialRow, SpatialCurvedRow y SpatialColumn para lograrlo.

XRFundamentalsApp.kt

import androidx.xr.compose.subspace.SpatialCurvedRow

...

Subspace {
    SpatialCurvedRow(
        curveRadius = 825.dp
    ) {
        SpatialPanel(...) { ... }
        SpatialPanel(...) { ... }
    }
}

Ejecuta la app una vez más. Deberías ver que los paneles están diseñados en una fila, uno detrás del otro.

Gracias a que usas el elemento componible SpatialCurvedRow, los paneles se curvan alrededor del usuario en lugar de permanecer en el mismo plano (como sería el caso de una SpatialRow), lo cual ofrece una experiencia más abarcativa.

2be1a054de7a8106.png

Haz que los paneles se puedan cambiar de tamaño

Para darles a los usuarios el control de la apariencia de su app, puedes ofrecer la posibilidad de cambiar el tamaño de los paneles usando el modificador resizable.

De forma predeterminada, los paneles de tamaño variable se pueden achicar a cero o expandir de forma indefinida. Por lo tanto, recomendamos que te tomes el tiempo para configurar los parámetros minimumSize y maximumSize adecuados en función del contenido que tendrán.

Consulta la documentación de referencia para obtener más detalles de todos los parámetros que admite el modificador resizable.

XRFundamentalsApp.kt

import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.resizable

...

SpatialPanel(
    modifier = SubspaceModifier
        ...
        .resizable()
)

466c1154c0e1fda3.gif

Haz que los paneles se puedan mover

Del mismo modo, puedes hacer que los paneles se muevan usando un MovePolicy.

XRFundamentalsApp.kt

import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.movable

...

SpatialPanel(
    modifier = SubspaceModifier
        ...
        .movable()
)

330b6a4d1a688a72.gif

Consulta la documentación de referencia para obtener más detalles sobre todos los parámetros que admite un MovePolicy.

8. Actualiza el manifiesto

Ahora que agregaste funciones espaciales a tu app, te recomendamos que actualices el manifiesto para indicar que tu app admite estas funciones. Esto permite que Google Play destaque tu app como diferenciada para dispositivos Android XR.

Para ello, agrega el siguiente elemento <uses-feature> en el archivo AndroidManifest.xml:

AndroidManifest.xml

<application ...>
    ...
    <uses-feature android:name="android.software.xr.api.spatial" android:required="false"/>
    ...
</application>

El uso de un valor de false para el atributo android:required indica que la app usa la función si está disponible, pero no impide que la app se distribuya a dispositivos que no tienen la función (p. ej., teléfonos). Esto te permite distribuir contenido a teléfonos y dispositivos de realidad extendida desde segmentos de dispositivos móviles. En cambio, si quieres usar los segmentos exclusivos de realidad extendida, configura android:required como true.

9. Felicitaciones

Para continuar aprendiendo a aprovechar al máximo Android XR, consulta el siguiente codelab, Descubre los aspectos básicos de Android XR: Parte 2 - Orbitadores y entornos espaciales, así como los siguientes recursos y ejercicios:

Lecturas adicionales

Desafíos

  • Usa los parámetros adicionales disponibles para los modificadores resizable y movable.
  • Agrega paneles adicionales.
  • Usa otros componentes espaciales como un diálogo espacial.

Documentos de referencia