Cómo desarrollar una IU con Jetpack Compose para XR

Con Jetpack Compose para XR, puedes compilar de forma declarativa tu IU y diseño espacial con conceptos conocidos de Compose, como filas y columnas. Esto te permite extender tu IU de Android existente al espacio 3D o compilar aplicaciones 3D envolventes completamente nuevas.

Si espacializas una app existente basada en objetos View de Android, tienes varias opciones de desarrollo. Puedes usar APIs de interoperabilidad, usar Compose y Views juntos, o trabajar directamente con la biblioteca de SceneCore. Consulta nuestra guía para trabajar con vistas para obtener más detalles.

Información acerca de los subespacios y los componentes espacializados

Cuando escribes tu app para Android XR, es importante comprender los conceptos de subespacio y componentes espacializados.

Información acerca del subespacio

Cuando desarrolles para Android XR, deberás agregar un subespacio a tu app o diseño. Un subespacio es una partición del espacio 3D dentro de tu app en la que puedes colocar contenido 3D, compilar diseños 3D y agregar profundidad a contenido que, de otro modo, sería 2D. Un subespacio solo se renderiza cuando la espacialización está habilitada. En Home Space o en dispositivos que no son XR, se ignora cualquier código dentro de ese subespacio.

Existen dos formas de crear un subespacio:

  • setSubspaceContent: Esta función crea un subespacio a nivel de la app. Se puede llamar a esta función en MainActivity de la misma manera que usas setContent. Un subespacio a nivel de la app no tiene límites de altura, ancho ni profundidad, lo que proporciona un lienzo infinito para el contenido espacial.
  • Subspace: Este elemento componible se puede colocar en cualquier lugar dentro de la jerarquía de la IU de tu app, lo que te permite mantener diseños para la IU 2D y espacial sin perder el contexto entre los archivos. Esto facilita compartir elementos como la arquitectura de la app existente entre XR y otros factores de forma sin necesidad de elevar el estado a través de todo el árbol de la IU ni volver a diseñar la arquitectura de tu app.

Para obtener más información, consulta Cómo agregar un subespacio a tu app.

Información acerca de los componentes espacializados

Elementos componibles de subespacio: Estos componentes solo se pueden renderizar en un subespacio. Deben encerrarse en Subspace o setSubspaceContent antes de colocarse en un diseño 2D. Un SubspaceModifier te permite agregar atributos como profundidad, desplazamiento y posicionamiento a tus elementos componibles de subespacio.

  • Nota sobre los modificadores de subespacio: Presta especial atención al orden de las APIs de SubspaceModifier.
    • El desplazamiento debe ocurrir primero en una cadena de modificadores
    • Mover y cambiar el tamaño deben ocurrir al final
    • La rotación se debe aplicar antes de la escala

Otros componentes espacializados no requieren que se los llame dentro de un subespacio. Consisten en elementos 2D convencionales unidos en un contenedor espacial. Estos elementos se pueden usar en diseños 2D o 3D si se definen para ambos. Cuando la espacialización no esté habilitada, se ignorarán sus componentes espacializados y se usarán sus contrapartes en 2D.

Crea un panel espacial

Un SpatialPanel es un subespacio componible que te permite mostrar contenido de la app. Por ejemplo, puedes mostrar la reproducción de video, imágenes fijas o cualquier otro contenido en un panel espacial.

Ejemplo de un panel de IU espacial

Puedes usar SubspaceModifier para cambiar el tamaño, el comportamiento y la posición del panel espacial, como se muestra en el siguiente ejemplo.

Subspace {
   SpatialPanel(
        SubspaceModifier
           .height(824.dp)
           .width(1400.dp)
           .movable()
           .resizable()
           ) {
          SpatialPanelContent()
      }
}

// 2D content placed within the spatial panel
@Composable
fun SpatialPanelContent(){
    Box(
        Modifier
            .background(color = Color.Black)
            .height(500.dp)
            .width(500.dp),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = "Spatial Panel",
            color = Color.White,
            fontSize = 25.sp
        )
    }
}

Puntos clave sobre el código

  • Nota sobre los modificadores de subespacio: Presta especial atención al orden de las APIs de SubspaceModifier.
    • El desplazamiento debe ocurrir primero en una cadena de modificadores.
    • Los modificadores móviles y de tamaño variable deben aparecer al final.
    • La rotación se debe aplicar antes de la escala.
  • Debido a que las APIs de SpatialPanel son componibles de subespacio, debes llamarlas dentro de Subspace o setSubspaceContent. Si los llamas fuera de un subespacio, se arrojará una excepción.
  • Agrega SubspaceModifier .movable o .resizable para permitir que el usuario cambie el tamaño del panel o lo mueva.
  • Consulta nuestra guía de diseño de paneles espaciales para obtener detalles sobre el tamaño y el posicionamiento. Consulta nuestra documentación de referencia para obtener más detalles sobre la implementación de códigos.

Crea un satélite en órbita

Un orbitador es un componente de IU espacial. Está diseñado para adjuntarse a un panel espacial correspondiente y contiene elementos de acción contextual y de navegación relacionados con ese panel espacial. Por ejemplo, si creaste un panel espacial para mostrar contenido de video, puedes agregar controles de reproducción de video dentro de un orbitador.

Ejemplo de una sonda orbital

Como se muestra en el siguiente ejemplo, llama a un orbitador dentro de un SpatialPanel para unir los controles del usuario, como la navegación. De esta manera, se extraen de tu diseño 2D y se adjuntan al panel espacial según tu configuración.

setContent {
    Subspace {
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
                .movable()
                .resizable()
        ) {
            SpatialPanelContent()
            OrbiterExample()
        }
    }
}

//2D content inside Orbiter
@Composable
fun OrbiterExample() {
    Orbiter(
        position = OrbiterEdge.Bottom,
        offset = 96.dp,
        alignment = Alignment.CenterHorizontally
    ) {
        Surface(Modifier.clip(CircleShape)) {
            Row(
                Modifier
                    .background(color = Color.Black)
                    .height(100.dp)
                    .width(600.dp),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = "Orbiter",
                    color = Color.White,
                    fontSize = 50.sp
                )
            }
        }
    }
}

Puntos clave sobre el código

  • Nota sobre los modificadores de subespacio: Presta especial atención al orden de las APIs de SubspaceModifier.
    • El desplazamiento debe ocurrir primero en una cadena de modificadores
    • Mover y cambiar el tamaño deben ocurrir al final
    • La rotación se debe aplicar antes de la escala
  • Debido a que los orbitadores son componentes espaciales de la IU, el código se puede reutilizar en diseños 2D o 3D. En un diseño 2D, tu app renderiza solo el contenido dentro del orbitador y omite el orbitador en sí.
  • Consulta nuestra guía de diseño para obtener más información sobre cómo usar y diseñar orbitadores.

Cómo agregar varios paneles espaciales a un diseño espacial

Puedes crear varios paneles espaciales y colocarlos dentro de un SpatialLayout con SpatialRow, SpatialColumn, SpatialBox y SpatialLayoutSpacer.

Ejemplo de varios paneles espaciales en un diseño espacial

En el siguiente ejemplo de código, se muestra cómo hacerlo.

Subspace {
    SpatialRow {
        SpatialColumn {
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Top Left")
            }
            SpatialPanel(SubspaceModifier.height(200.dp).width(400.dp)) {
                SpatialPanelContent("Middle Left")
            }
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Bottom Left")
            }
        }
        SpatialColumn {
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Top Right")
            }
            SpatialPanel(SubspaceModifier.height(200.dp).width(400.dp)) {
                SpatialPanelContent("Middle Right")
            }
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Bottom Right")
            }
        }
    }
}

@Composable
fun SpatialPanelContent(text: String) {
    Column(
        Modifier
            .background(color = Color.Black)
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Panel",
            color = Color.White,
            fontSize = 15.sp
        )
        Text(
            text = text,
            color = Color.White,
            fontSize = 25.sp,
            fontWeight = FontWeight.Bold
        )
    }
}

Puntos clave sobre el código

  • SpatialRow, SpatialColumn, SpatialBox y SpatialLayoutSpacer son elementos componibles de subespacio y deben colocarse dentro de un subespacio.
  • Usa SubspaceModifier para personalizar el diseño.
  • Para diseños con varios paneles en una fila, te recomendamos que configures un radio de curva de 825 dp con un SubspaceModifier para que los paneles rodeen al usuario. Consulta nuestra guía de diseño para obtener más detalles.

Usa un volumen para colocar un objeto 3D en tu diseño

Para colocar un objeto 3D en tu diseño, deberás usar un elemento componible de subespacio llamado volumen. Este es un ejemplo de cómo hacerlo.

Ejemplo de un objeto 3D en un diseño

Subspace {
    SpatialPanel(
        SubspaceModifier.height(1500.dp).width(1500.dp)
            .resizable().movable()
    ) {
        ObjectInAVolume(true)
            Box(
                Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "Welcome",
                    fontSize = 50.sp,
                )
            }
        }
    }
}

@Composable
fun ObjectInAVolume(show3DObject: Boolean) {
    val xrCoreSession = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    if (show3DObject) {
        Subspace {
            Volume(
                modifier = SubspaceModifier
                    .offset(volumeXOffset, volumeYOffset, volumeZOffset) //
Relative position
                    .scale(1.2f) // Scale to 120% of the size

            ) { parent ->
                scope.launch {
                   // Load your 3D Object here
                }
            }
        }
    }
}

Puntos clave sobre el código

  • Nota sobre los modificadores de subespacio: Presta especial atención al orden de las APIs de SubspaceModifier.
    • El desplazamiento debe ocurrir primero en una cadena de modificadores
    • Mover y cambiar el tamaño deben ocurrir al final
    • La rotación se debe aplicar antes de la escala
  • Consulta Cómo agregar contenido 3D para comprender mejor cómo cargar contenido 3D dentro de un volumen.

Cómo agregar otros componentes espaciales de la IU

Los componentes de la IU espacial se pueden colocar en cualquier lugar de la jerarquía de la IU de tu aplicación. Estos elementos se pueden volver a usar en tu IU 2D, y sus atributos espaciales solo serán visibles cuando se habiliten las capacidades espaciales. Esto te permite agregar elevación a menús, diálogos y otros componentes sin necesidad de escribir el código dos veces. Consulta los siguientes ejemplos de IU espacial para comprender mejor cómo usar estos elementos.

Componente de IU

Cuándo se habilita la espacialización

En el entorno 2D

SpatialDialog

El panel se empujará ligeramente hacia atrás en la profundidad en Z para mostrar un diálogo elevado.

Recurre a 2D Dialog.

SpatialPopUp

El panel se empujará ligeramente hacia atrás en la profundidad en Z para mostrar una ventana emergente elevada.

Recurre a un PopUp 2D.

SpatialElevation

Se puede configurar SpatialElevationLevel para agregar elevación.

Muestras sin elevación espacial.

SpatialDialog

Este es un ejemplo de un diálogo que se abre después de una breve demora. Cuando se usa SpatialDialog, el diálogo aparece en la misma profundidad en Z que el panel espacial, y el panel se aleja 125 dp cuando se habilita la espacialización. SpatialDialog se puede usar cuando la espacialización tampoco está habilitada y se recurre a su contraparte en 2D: Dialog.

@Composable
fun DelayedDialog() {
   var showDialog by remember { mutableStateOf(false) }
   LaunchedEffect(Unit) {
       Handler(Looper.getMainLooper()).postDelayed({
           showDialog = true
       }, 3000)
   }
   if (showDialog) {
       SpatialDialog (
           onDismissRequest = { showDialog = false },
           SpatialDialogProperties(
               dismissOnBackPress = true)
       ){
           Box(Modifier
               .height(150.dp)
               .width(150.dp)
           ) {
               Button(onClick = { showDialog = false }) {
                   Text("OK")
               }
           }
       }
   }
}

Puntos clave sobre el código

Crea paneles y diseños personalizados

Para crear paneles personalizados que Compose para XR no admite, puedes trabajar directamente con PanelEntities y el grafo de escenas con las APIs de SceneCore.

Consulta también