Con Jetpack Compose para XR, puedes compilar de forma declarativa tu IU y diseño espaciales con conceptos conocidos de Compose, como filas y columnas. Esto te permite extender tu IU de Android existente al espacio 3D o compilar aplicaciones envolventes en 3D completamente nuevas.
Si vas a espacializar una app existente basada en Android Views, 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.
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.
Acerca del subespacio
Cuando desarrolles para Android XR, deberás agregar un Subspace
a tu app o diseño. Un subespacio es una partición de espacio 3D dentro de tu app en la que puedes colocar contenido 3D, compilar diseños 3D y agregar profundidad a contenido que, de otra forma, es 2D. Un subespacio solo se renderiza cuando la espacialización está habilitada. En el espacio principal o en dispositivos que no son de XR, se ignora cualquier código dentro de ese subespacio.
Existen dos formas de crear un subespacio:
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 espacial y en 2D sin perder el contexto entre los archivos. Esto facilita el uso compartido de elementos como la arquitectura de la app existente entre la XR y otros factores de forma sin necesidad de elevar el estado a través de todo el árbol de la IU ni rediseñar la app.ApplicationSubspace
: Esta función crea un subespacio a nivel de la app únicamente y debe colocarse en el nivel superior de la jerarquía de la IU espacial de tu aplicación.ApplicationSubspace
renderiza contenido espacial conVolumeConstraints
opcional. A diferencia deSubspace
,ApplicationSubspace
no se puede anidar dentro de otroSubspace
oApplicationSubspace
.
Para obtener más información, consulta Cómo agregar un subespacio a tu app.
Acerca de los componentes espacializados
Elementos componibles de subespacio: Estos componentes solo se pueden renderizar en un subespacio.
Deben estar encerrados dentro de Subspace
o setSubspaceContent()
antes de colocarse dentro de un diseño 2D. Un SubspaceModifier
te permite agregar atributos como profundidad, desplazamiento y posicionamiento a tus elementos componibles de subespacio.
No es necesario llamar a otros componentes espacializados dentro de un subespacio. Consisten en elementos 2D convencionales envueltos 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 funciones espacializadas y se volverán a usar sus equivalentes en 2D.
Cómo crear 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 videos, imágenes fijas o cualquier otro contenido en un panel 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() } }
@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
- Debido a que las APIs de
SpatialPanel
son elementos componibles de subespacio, debes llamarlas dentro deSubspace
. Si se llama a estos métodos fuera de un subespacio, se arroja una excepción. - El tamaño de
SpatialPanel
se estableció con las especificacionesheight
ywidth
enSubspaceModifier
. Si se omiten estas especificaciones, las medidas de su contenido determinarán el tamaño del panel. - Permite que el usuario cambie el tamaño o mueva el panel agregando los modificadores
movable
oresizable
. - Consulta nuestra guía de diseño de paneles espaciales para obtener detalles sobre el tamaño y la posición. Consulta nuestra documentación de referencia para obtener más detalles sobre la implementación de códigos.
Cómo funciona un modificador de subespacio móvil
Cuando un usuario aleja un panel, de forma predeterminada, un modificador de subespacio movible lo ajusta de manera similar a como el sistema ajusta los paneles en el espacio principal. Todo el contenido infantil hereda este comportamiento. Para inhabilitar esta opción, establece el parámetro scaleWithDistance
en false
.
Crea un orbitador
Un orbitador es un componente de la 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 navegación y de acción contextual relacionados con la entidad a la que está anclado. Por ejemplo, si creaste un panel espacial para mostrar contenido de video, podrías agregar controles de reproducción de video dentro de un orbitador.
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. Si lo haces, se extraerán de tu diseño 2D y se adjuntarán al panel espacial según tu configuración.
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp) .movable() .resizable() ) { SpatialPanelContent() OrbiterExample() } }
@Composable fun OrbiterExample() { Orbiter( position = ContentEdge.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
- Dado 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 solo renderiza 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
.
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
ySpatialLayoutSpacer
son todos elementos componibles de subespacio y deben colocarse dentro de un subespacio.- Usa
SubspaceModifier
para personalizar tu diseño. - Para los diseños con varios paneles en 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.
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, ) } } }
@OptIn(ExperimentalSubspaceVolumeApi::class) @Composable fun ObjectInAVolume(show3DObject: Boolean) {
Información adicional
- Consulta Cómo agregar modelos 3D a tu app para comprender mejor cómo cargar contenido 3D dentro de un volumen.
Agrega una superficie para el contenido de imágenes o videos
Un SpatialExternalSurface
es un elemento componible de subespacio que crea y administra el Surface
en el que tu app puede dibujar contenido, como una imagen o un video. SpatialExternalSurface
admite contenido estereoscópico o monoscópico.
En este ejemplo, se muestra cómo cargar un video estereoscópico lado a lado con Media3 Exoplayer y SpatialExternalSurface
:
@OptIn(ExperimentalComposeApi::class) @Composable fun SpatialExternalSurfaceContent() { val context = LocalContext.current Subspace { SpatialExternalSurface( modifier = SubspaceModifier .width(1200.dp) // Default width is 400.dp if no width modifier is specified .height(676.dp), // Default height is 400.dp if no height modifier is specified // Use StereoMode.Mono, StereoMode.SideBySide, or StereoMode.TopBottom, depending // upon which type of content you are rendering: monoscopic content, side-by-side stereo // content, or top-bottom stereo content stereoMode = StereoMode.SideBySide, ) { val exoPlayer = remember { ExoPlayer.Builder(context).build() } val videoUri = Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) // Represents a side-by-side stereo video, where each frame contains a pair of // video frames arranged side-by-side. The frame on the left represents the left // eye view, and the frame on the right represents the right eye view. .path("sbs_video.mp4") .build() val mediaItem = MediaItem.fromUri(videoUri) // onSurfaceCreated is invoked only one time, when the Surface is created onSurfaceCreated { surface -> exoPlayer.setVideoSurface(surface) exoPlayer.setMediaItem(mediaItem) exoPlayer.prepare() exoPlayer.play() } // onSurfaceDestroyed is invoked when the SpatialExternalSurface composable and its // associated Surface are destroyed onSurfaceDestroyed { exoPlayer.release() } } } }
Puntos clave sobre el código
- Establece
StereoMode
enMono
,SideBySide
oTopBottom
según el tipo de contenido que renderices:Mono
: El fotograma de la imagen o el video consta de una sola imagen idéntica que se muestra en ambos ojos.SideBySide
: El fotograma de imagen o video contiene un par de imágenes o fotogramas de video dispuestos uno al lado del otro, en el que la imagen o el fotograma de la izquierda representa la vista del ojo izquierdo, y la imagen o el fotograma de la derecha representa la vista del ojo derecho.TopBottom
: El fotograma de imagen o video contiene un par de imágenes o fotogramas de video apilados verticalmente, en los que la imagen o el fotograma de la parte superior representan la vista del ojo izquierdo, y la imagen o el fotograma de la parte inferior representan la vista del ojo derecho.
SpatialExternalSurface
solo admite superficies rectangulares.- Este
Surface
no captura eventos de entrada. - No es posible sincronizar los cambios de
StereoMode
con la renderización de la aplicación o la decodificación de video. - Este elemento componible no se puede renderizar delante de otros paneles, por lo que no debes usar modificadores movibles si hay otros paneles en el diseño.
Agrega una superficie para el contenido de video protegido por DRM
SpatialExternalSurface
también admite la reproducción de transmisiones de video protegidas por DRM. Para habilitar esta opción, debes crear una superficie segura que se renderice en búferes gráficos protegidos. Esto evita que el contenido se grabe en pantalla o que los componentes del sistema no seguros accedan a él.
Para crear una superficie segura, establece el parámetro surfaceProtection
en SurfaceProtection.Protected
en el elemento SpatialExternalSurface
componible.
Además, debes configurar Media3 ExoPlayer con la información de DRM adecuada para controlar la adquisición de licencias desde un servidor de licencias.
En el siguiente ejemplo, se muestra cómo configurar SpatialExternalSurface
y ExoPlayer
para reproducir una transmisión de video protegida por DRM:
@OptIn(ExperimentalComposeApi::class) @Composable fun DrmSpatialVideoPlayer() { val context = LocalContext.current Subspace { SpatialExternalSurface( modifier = SubspaceModifier .width(1200.dp) .height(676.dp), stereoMode = StereoMode.SideBySide, surfaceProtection = SurfaceProtection.Protected ) { val exoPlayer = remember { ExoPlayer.Builder(context).build() } // Define the URI for your DRM-protected content and license server. val videoUri = "https://your-content-provider.com/video.mpd" val drmLicenseUrl = "https://your-license-server.com/license" // Build a MediaItem with the necessary DRM configuration. val mediaItem = MediaItem.Builder() .setUri(videoUri) .setDrmConfiguration( MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID) .setLicenseUri(drmLicenseUrl) .build() ) .build() onSurfaceCreated { surface -> // The created surface is secure and can be used by the player. exoPlayer.setVideoSurface(surface) exoPlayer.setMediaItem(mediaItem) exoPlayer.prepare() exoPlayer.play() } onSurfaceDestroyed { exoPlayer.release() } } } }
Puntos clave sobre el código
- Superficie protegida: Es fundamental establecer
surfaceProtection = SurfaceProtection.Protected
enSpatialExternalSurface
para que elSurface
subyacente esté respaldado por búferes seguros adecuados para el contenido protegido por DRM. - Configuración de DRM: Debes configurar
MediaItem
con el esquema de DRM (por ejemplo,C.WIDEVINE_UUID
) y el URI de tu servidor de licencias. ExoPlayer usa esta información para administrar la sesión de DRM. - Contenido seguro: Cuando se renderiza en una superficie protegida, el contenido de video se decodifica y se muestra en una ruta segura, lo que ayuda a satisfacer los requisitos de licencias de contenido. Esto también evita que el contenido aparezca en las capturas de pantalla.
Agrega otros componentes de la IU espacial
Los componentes de IU espaciales se pueden colocar en cualquier lugar de la jerarquía de IU de tu aplicación. Estos elementos se pueden reutilizar 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 tu código dos veces. Consulta los siguientes ejemplos de IU espacial para comprender mejor cómo usar estos elementos.
Componente de IU |
Cuando la espacialización está habilitada |
En un entorno 2D |
---|---|---|
|
El panel se desplazará ligeramente hacia atrás en la profundidad Z para mostrar un diálogo elevado. |
Recurre a |
|
El panel se desplazará ligeramente hacia atrás en la profundidad Z para mostrar una ventana emergente elevada. |
Se recurre a un |
|
Se puede configurar |
Muestra 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 Z que el panel espacial, y el panel se desplaza hacia atrás en 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) { delay(3000) showDialog = true } 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
- Este es un ejemplo de
SpatialDialog
. El uso deSpatialPopup
ySpatialElevation
es muy similar. Consulta nuestra referencia de la API para obtener más detalles.
Crea paneles y diseños personalizados
Para crear paneles personalizados que no son compatibles con Compose para XR, puedes trabajar directamente con instancias de PanelEntity
y el gráfico de escena con las APIs de SceneCore
.
Ancla los orbitadores a diseños espaciales y otras entidades
Puedes anclar un 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 principal más cercana al lugar donde lo declaraste.
El comportamiento del orbitador se determina según dónde lo declares:
- En un diseño 2D incluido en un
SpatialPanel
(como se muestra en un fragmento de código anterior), el orbitador se fija a eseSpatialPanel
. - En un
Subspace
, el orbitador se ancla a la entidad principal más cercana, que es el diseño espacial en el que se declara el orbitador.
En el siguiente ejemplo, se muestra cómo anclar un orbitador a una fila espacial:
Subspace { SpatialRow { Orbiter( position = ContentEdge.Top, offset = 8.dp, offsetType = OrbiterOffsetType.InnerEdge, shape = SpatialRoundedCornerShape(size = CornerSize(50)) ) { Text( "Hello World!", style = MaterialTheme.typography.titleMedium, 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, el orbitador se ancla a su entidad principal 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
ySpatialBox
, 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
- Agrega modelos 3D a tu app
- Cómo desarrollar una IU de apps para Android basadas en Views
- Implementa Material Design para XR