Desenvolver a interface com o Jetpack Compose para XR

Com o Jetpack Compose para XR, é possível criar de forma declarativa a interface espacial e o layout usando conceitos conhecidos do Compose, como linhas e colunas. Isso permite que você estenda a interface do Android para o espaço 3D ou crie aplicativos 3D imersivos completamente novos.

Se você estiver espacializando um app baseado em Android Views, terá várias opções de desenvolvimento. Você pode usar APIs de interoperabilidade, usar o Compose e as visualizações juntos ou trabalhar diretamente com a biblioteca SceneCore. Consulte nosso guia de trabalho com visualizações para mais detalhes.

Sobre subespaços e componentes espaciais

Ao criar seu app para o Android XR, é importante entender os conceitos de subespaço e componentes espacializados.

Sobre o Subspace

Ao desenvolver para o Android XR, você precisa adicionar um subspace ao app ou layout. Um subespaço é uma partição do espaço 3D no app em que você pode colocar conteúdo 3D, criar layouts 3D e adicionar profundidade a conteúdo 2D. Um subespaço é renderizado somente quando a espacialização está ativada. No espaço doméstico ou em dispositivos que não são XR, qualquer código nesse subspace é ignorado.

Há duas maneiras de criar um subespaço:

  • setSubspaceContent(): essa função cria um subespaço no nível do app. Ele pode ser chamado na atividade principal da mesma forma que você usa setContent(). Um subspace no nível do app não tem limite de altura, largura e profundidade, fornecendo essencialmente uma tela infinita para conteúdo espacial.
  • Subspace: esse elemento combinável pode ser colocado em qualquer lugar na hierarquia da interface do app, permitindo que você mantenha layouts para interfaces 2D e espaciais sem perder o contexto entre os arquivos. Isso facilita o compartilhamento de coisas como a arquitetura de apps existente entre XR e outros formatos sem precisar elevar o estado em toda a árvore de interface ou reprojetar o app.

Para mais informações, consulte Adicionar um subspace ao app.

Sobre os componentes espaciais

Combináveis de subespaço: esses componentes só podem ser renderizados em um subespaço. Elas precisam ser incluídas em Subspace ou setSubspaceContent antes de serem colocadas em um layout 2D. Um SubspaceModifier permite adicionar atributos como profundidade, deslocamento e posicionamento aos elementos combináveis do subespaço.

Outros componentes espaciais não precisam ser chamados em um subspace. Eles consistem em elementos 2D convencionais unidos em um contêiner espacial. Esses elementos podem ser usados em layouts 2D ou 3D, se definidos para ambos. Quando a espacialização não está ativada, os recursos espaciais são ignorados e voltam para as versões 2D.

Criar um painel espacial

Um SpatialPanel é um elemento combinável de subespaço que permite mostrar o conteúdo do app. Por exemplo, é possível mostrar a reprodução de vídeo, imagens estáticas ou qualquer outro conteúdo em um painel espacial.

Exemplo de painel de interface espacial

Use SubspaceModifier para mudar o tamanho, o comportamento e o posicionamento do painel espacial, conforme mostrado no exemplo a seguir.

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

Pontos principais sobre o código

Criar um orbitador

Um orbitador é um componente de interface espacial. Ele foi projetado para ser anexado a um painel espacial, layout ou outra entidade correspondente. Um orbitador normalmente contém itens de navegação e ação contextual relacionados à entidade a que ele está anexado. Por exemplo, se você criou um painel espacial para exibir conteúdo em vídeo, é possível adicionar controles de reprodução de vídeo dentro de um orbitador.

Exemplo de um orbitador

Como mostrado no exemplo abaixo, chame um orbitador dentro do layout 2D em um SpatialPanel para agrupar os controles do usuário, como a navegação. Isso extrai os elementos do layout 2D e os anexa ao painel espacial de acordo com a configuração.

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

Pontos principais sobre o código

  • Como os orbiters são componentes de interface espacial, o código pode ser reutilizado em layouts 2D ou 3D. Em um layout 2D, o app renderiza apenas o conteúdo dentro do orbitador e ignora o próprio orbitador.
  • Confira nossas orientações de design para mais informações sobre como usar e projetar orbitadores.

Adicionar vários painéis espaciais a um layout espacial

É possível criar vários painéis espaciais e colocá-los em um layout espacial usando SpatialRow, SpatialColumn, SpatialBox e SpatialLayoutSpacer.

Exemplo de vários painéis espaciais em um layout espacial

O exemplo de código abaixo mostra como fazer isso.

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

Pontos principais sobre o código

Use um volume para colocar um objeto 3D no layout

Para colocar um objeto 3D no layout, você precisa usar um elemento combinável de subespaço chamado volume. Confira um exemplo de como fazer isso.

Exemplo de objeto 3D em um 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) {
    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
                }
            }
        }
    }
}

Pontos principais sobre o código

Adicionar outros componentes de interface espacial

Os componentes de IU espacial podem ser colocados em qualquer lugar na hierarquia de IU do aplicativo. Esses elementos podem ser reutilizados na interface 2D, e os atributos espaciais só serão visíveis quando os recursos espaciais estiverem ativados. Isso permite adicionar elevação a menus, caixas de diálogo e outros componentes sem precisar escrever o código duas vezes. Confira os exemplos de interface espacial a seguir para entender melhor como usar esses elementos.

Componente da interface

Quando a espacialização está ativada

Em um ambiente 2D

SpatialDialog

O painel vai se afastar um pouco na profundidade Z para mostrar uma caixa de diálogo elevada.

Volta para 2D Dialog.

SpatialPopUp

O painel vai ser empurrado ligeiramente para trás na profundidade z para mostrar um pop-up elevado.

Volta para um PopUp 2D.

SpatialElevation

SpatialElevationLevel pode ser definido para adicionar elevação.

Mostra sem elevação espacial.

SpatialDialog

Este é um exemplo de caixa de diálogo que é aberta após um breve atraso. Quando SpatialDialog é usado, a caixa de diálogo aparece na mesma profundidade que o painel espacial, e o painel é empurrado para trás em 125dp quando a espacialização é ativada. SpatialDialog também pode ser usado quando a espacialização não está ativada. Nesse caso, SpatialDialog volta para a versão 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")
               }
           }
       }
   }
}

Pontos principais sobre o código

Criar painéis e layouts personalizados

Para criar painéis personalizados que não têm suporte do Compose para XR, você pode trabalhar diretamente com PanelEntities e a cena usando as APIs SceneCore.

Fixar orbitadores em layouts espaciais e outras entidades

É possível ancorar um orbitador a qualquer entidade declarada no Compose. Isso envolve declarar um orbitador em um layout espacial de elementos da interface, como SpatialRow, SpatialColumn ou SpatialBox. O Orbiter é ancorado à entidade pai mais próxima de onde foi declarado.

O comportamento do orbitador é determinado por onde você o declara:

  • Em um layout 2D agrupado em um SpatialPanel (como mostrado em um snippet de código anterior), o orbitador é ancorado a esse SpatialPanel.
  • Em um Subspace, o orbitador é ancorado à entidade pai mais próxima, que é o layout espacial em que o orbitador é declarado.

O exemplo a seguir mostra como ancorar um orbitador a uma linha 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)
            )
        }
    }
}

Pontos principais sobre o código

  • Quando você declara um orbitador fora de um layout 2D, ele é ancorado à entidade pai mais próxima. Nesse caso, o orbitador é ancorado na parte de cima do SpatialRow em que ele é declarado.
  • Layouts espaciais, como SpatialRow, SpatialColumn, SpatialBox, têm entidades sem conteúdo associadas a eles. Portanto, um orbitador declarado em um layout espacial é ancorado a esse layout.

Veja também