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 expandir 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 tu actividad principal 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 de rediseñ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.

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 elemento componible de subespacio 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

Crea una órbita

Un orbitador es un componente de IU espacial. Está diseñado para adjuntarse a un panel espacial, un diseño o alguna otra entidad correspondiente. Por lo general, un orbitador contiene elementos de acción contextuales y de navegación relacionados con la entidad a la que está anclado. 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 del diseño 2D en 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

  • 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 lo ignora.
  • 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 diseño espacial 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 establezcas 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

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 también se puede usar cuando la espacialización no está habilitada, en cuyo caso SpatialDialog 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.

Cómo anclar orbitadores a diseños espaciales y otras entidades

Puedes fijar un objeto orbitador a cualquier entidad declarada en Compose. Esto implica declarar un orbitador en un diseño espacial de elementos de la IU, como SpatialRow, SpatialColumn o SpatialBox. El orbitador se ancla a la entidad superior más cercana a la que lo declaraste.

El comportamiento del orbitador se determina según dónde lo declares:

  • En un diseño 2D unido a un SpatialPanel (como se muestra en un fragmento de código anterior), el orbitador se ancla a ese SpatialPanel.
  • En un Subspace, el orbitador se ancla a la entidad superior más cercana, que es el diseño espacial en el que se declara el orbitador.

En el siguiente ejemplo, se muestra cómo fijar un orbitador a una fila espacial:

Subspace {
    SpatialRow {
        Orbiter(
            position = OrbiterEdge.Top,
            offset = EdgeOffset.inner(8.dp),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.titleLarge,
                modifier = Modifier
                    .background(Color.White)
                    .padding(16.dp)
            )
        }
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
        ) {
            Box(
                modifier = Modifier
                    .background(Color.Red)
            )
        }
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
        ) {
            Box(
                modifier = Modifier
                    .background(Color.Blue)
            )
        }
    }
}

Puntos clave sobre el código

  • Cuando declaras un orbitador fuera de un diseño 2D, este se ancla a su entidad superior más cercana. En este caso, el orbitador se ancla a la parte superior del SpatialRow en el que se declara.
  • Los diseños espaciales, como SpatialRow, SpatialColumn y SpatialBox, tienen entidades sin contenido asociadas. Por lo tanto, un orbitador declarado en un diseño espacial se ancla a ese diseño.

Consulta también