Sviluppare l'interfaccia utente con Jetpack Compose per XR

Con Jetpack Compose per XR, puoi creare in modo dichiarativo l'interfaccia utente e il layout spaziali utilizzando concetti di Compose già noti, come righe e colonne. In questo modo puoi estendere la tua interfaccia utente Android esistente nello spazio 3D o creare applicazioni 3D immersive completamente nuove.

Se stai spazializzando un'app basata su Android Views esistente, hai a disposizione diverse opzioni di sviluppo. Puoi utilizzare le API di interoperabilità, Compose e le visualizzazioni insieme o lavorare direttamente con la libreria SceneCore. Per ulteriori dettagli, consulta la nostra guida all'utilizzo delle visualizzazioni.

Informazioni su sottospazi e componenti spazializzati

Quando scrivi la tua app per Android XR, è importante comprendere i concetti di spazio sottostante e componenti spazializzati.

Informazioni sullo spazio sottostante

Quando sviluppi per Android XR, devi aggiungere un sottospazio all'app o al layout. Un sottospazio è una partizione dello spazio 3D all'interno dell'app in cui puoi collocare contenuti 3D, creare layout 3D e aggiungere profondità a contenuti altrimenti 2D. Un subspazio viene visualizzato solo quando la spazializzazione è attivata. In Home Space o su dispositivi non XR, qualsiasi codice all'interno di questo sottospazio viene ignorato.

Esistono due modi per creare un sottospazio:

  • setSubspaceContent(): questa funzione crea un sottospazio a livello di app. Puoi chiamarlo nella tua attività principale nello stesso modo in cui utilizzi setContent(). Uno spazio sottospaziale a livello di app è illimitato in altezza, larghezza e profondità, fornendo essenzialmente una tela infinita per i contenuti spaziali.
  • Subspace: questo composable può essere posizionato in qualsiasi punto della gerarchia dell'interfaccia utente dell'app, consentendoti di mantenere i layout per l'interfaccia utente 2D e spaziale senza perdere il contesto tra i file. In questo modo è più facile condividere elementi come l'architettura dell'app esistente tra XR e altri fattori di forma senza dover eseguire l'hoisting dello stato nell'intera struttura dell'interfaccia utente o ristrutturare l'app.

Per ulteriori informazioni, vedi Aggiungere uno spazio secondario all'app.

Informazioni sui componenti spazializzati

Composabili in sottospazio: questi componenti possono essere visualizzati solo in un sottospazio. Devono essere racchiusi tra Subspace o setSubspaceContent prima di essere collocati in un layout 2D. Un SubspaceModifier ti consente di aggiungere attributi come profondità, offset e posizionamento ai composabili sottospazio.

Gli altri componenti spazializzati non richiedono di essere chiamati all'interno di un sottospazio. Sono costituiti da elementi 2D convenzionali racchiusi in un contenitore spaziale. Questi elementi possono essere utilizzati all'interno di layout 2D o 3D se definiti per entrambi. Se la spazializzazione non è attivata, le relative funzionalità spazializzate verranno ignorate e verrà eseguito il fallback alle relative controparti 2D.

Creare un riquadro spaziale

Un SpatialPanel è un componente componibile sottospaziale che ti consente di visualizzare i contenuti dell'app. Ad esempio, puoi visualizzare la riproduzione di video, immagini fisse o qualsiasi altro contenuto in un riquadro spaziale.

Esempio di riquadro dell'interfaccia utente spaziale

Puoi utilizzare SubspaceModifier per modificare le dimensioni, il comportamento e il posizionamento del riquadro spaziale, come mostrato nell'esempio seguente.

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

Punti chiave del codice

Come funziona un modificatore di sottospazio mobile

Quando un utente allontana un riquadro da sé, per impostazione predefinita un modificatore di sottospazio mobile ne modifica le dimensioni in modo simile a come vengono ridimensionati i riquadri dal sistema nello spazio di casa. Tutti i contenuti per bambini ereditano questo comportamento. Per disattivare questa funzionalità, imposta il parametro scaleWithDistance su false.

Creare un orbiter

Un orbiter è un componente dell'interfaccia utente spaziale. È progettato per essere collegato a un pannello spaziale, a un layout o a un'altra entità corrispondente. Un orbiter solitamente contiene elementi di navigazione e azioni contestuali correlati all'entità a cui è ancorato. Ad esempio, se hai creato un riquadro spaziale per visualizzare contenuti video, puoi aggiungere i controlli di riproduzione video all'interno di un orbiter.

Esempio di orbiter

Come mostrato nell'esempio seguente, chiama un orbiter all'interno del layout 2D in un SpatialPanel per racchiudere i controlli utente come la navigazione. In questo modo, li estrae dal layout 2D e li collega al riquadro spaziale in base alla tua configurazione.

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

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

Punti chiave del codice

  • Poiché gli orbiter sono componenti dell'interfaccia utente spaziale, il codice può essere riutilizzato in layout 2D o 3D. In un layout 2D, l'app esegue il rendering solo dei contenuti all'interno dell'orbiter e ignora l'orbiter stesso.
  • Consulta le nostre linee guida per la progettazione per saperne di più su come utilizzare e progettare gli orbiter.

Aggiungere più riquadri spaziali a un layout spaziale

Puoi creare più riquadri spaziali e posizionarli all'interno di un layout spaziale utilizzando SpatialRow, SpatialColumn, SpatialBox e SpatialLayoutSpacer.

Esempio di più pannelli spaziali in un layout spaziale

Il seguente esempio di codice mostra come eseguire questa operazione.

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

Punti chiave del codice

Utilizzare un volume per posizionare un oggetto 3D nel layout

Per posizionare un oggetto 3D nel layout, devi utilizzare un componibile sottospazio chiamato volume. Ecco un esempio di come fare.

Esempio di un oggetto 3D in un layout

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

Informazioni aggiuntive

Aggiungere una superficie per contenuti di immagini o video

Un SpatialExternalSurface è un componente componibile dello spazio sottostante che crea e gestisce il Surface in cui la tua app può inserire contenuti, ad esempio un'immagine o un video. SpatialExternalSurface supporta contenuti stereoscopici o monoscopici.

Questo esempio mostra come caricare un video stereoscopico affiancato utilizzando Media3 Exoplayer e SpatialExternalSurface:

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

Punti chiave del codice

  • Imposta StereoMode su Mono, SideBySide o TopBottom a seconda del tipo di contenuti che stai visualizzando:
    • Mono: l'inquadratura dell'immagine o del video è costituita da un'unica immagine identica mostrata a entrambi gli occhi.
    • SideBySide: l'immagine o il frame video contiene una coppia di immagini o frame video disposti affiancati, dove l'immagine o il frame a sinistra rappresenta la visuale dell'occhio sinistro e l'immagine o il frame a destra rappresenta la visuale dell'occhio destro.
    • TopBottom: l'immagine o il frame video contiene una coppia di immagini o frame video impilate verticalmente, dove l'immagine o il frame in alto rappresenta la vista dell'occhio sinistro e l'immagine o il frame in basso rappresenta la vista dell'occhio destro.
  • SpatialExternalSurface supporta solo le piattaforme rettangolari.
  • Questo Surface non acquisisce gli eventi di input.
  • Non è possibile sincronizzare le modifiche di StereoMode con il rendering dell'applicazione o la decodifica video.
  • Questo composable non può essere visualizzato davanti ad altri riquadri, quindi non devi utilizzare modificatori spostabili se nel layout sono presenti altri riquadri.

Aggiungere altri componenti dell'interfaccia utente spaziale

I componenti dell'interfaccia utente spaziale possono essere posizionati in qualsiasi punto della gerarchia dell'interfaccia utente dell'applicazione. Questi elementi possono essere riutilizzati nell'interfaccia utente 2D e i relativi attributi spaziali saranno visibili solo quando le funzionalità spaziali sono attivate. In questo modo puoi aggiungere un'elevazione a menu, finestre di dialogo e altri componenti senza dover scrivere il codice due volte. Consulta i seguenti esempi di UI spaziale per comprendere meglio come utilizzare questi elementi.

Componente UI

Quando la spazializzazione è attivata

In ambiente 2D

SpatialDialog

Il riquadro si spingerà leggermente indietro in profondità Z per visualizzare una finestra di dialogo in primo piano

Torna alla modalità 2D Dialog.

SpatialPopup

Il riquadro si spingerà leggermente indietro in profondità z per visualizzare un popup rialzato

Torna a un Popup 2D.

SpatialElevation

SpatialElevationLevel può essere impostato per aggiungere l'elevazione.

Programmi senza elevazione spaziale.

SpatialDialog

Questo è un esempio di finestra di dialogo che si apre dopo un breve ritardo. Quando viene utilizzato SpatialDialog, la finestra di dialogo viene visualizzata alla stessa profondità z del pannello spaziale, che viene spostato indietro di 125 dp quando la spazializzazione è attivata. SpatialDialog può essere utilizzato anche quando la spazializzazione non è attiva, in questo caso SpatialDialog passa alla sua controparte 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")
                }
            }
        }
    }
}

Punti chiave del codice

Creare pannelli e layout personalizzati

Per creare riquadri personalizzati non supportati da Compose for XR, puoi lavorare direttamente con PanelEntities e la scena utilizzando le API SceneCore.

Ancorare gli orbitanti a layout spaziali e altre entità

Puoi ancorare un orbiter a qualsiasi entità dichiarata in Compose. Ciò comporta la dichiarazione di un orbiter in un layout spaziale di elementi dell'interfaccia utente come SpatialRow, SpatialColumn o SpatialBox. L'orbiter si ancora all'entità principale più vicina al punto in cui l'hai dichiarata.

Il comportamento dell'orbiter è determinato dal punto in cui lo dichiari:

  • In un layout 2D racchiuso in un SpatialPanel (come mostrato in uno snippet di codice precedente), l'orbiter si ancora a quel SpatialPanel.
  • In un Subspace, l'orbiter si ancora all'entità principale più vicina, ovvero al layout spaziale in cui è dichiarato.

L'esempio seguente mostra come ancorare un orbiter a una riga spaziale:

Subspace {
    SpatialRow {
        Orbiter(
            position = OrbiterEdge.Top,
            offset = EdgeOffset.inner(8.dp),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.h2,
                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)
            )
        }
    }
}

Punti chiave del codice

  • Quando dichiari un orbiter al di fuori di un layout 2D, l'orbiter si ancora all'entità principale più vicina. In questo caso, l'orbiter si ancora alla parte superiore del SpatialRow in cui è dichiarato.
  • A tutti i layout spaziali come SpatialRow, SpatialColumn, SpatialBox sono associate entità senza contenuti. Pertanto, un orbiter dichiarato in un layout spaziale si ancora a quel layout.

Vedi anche