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 estender a interface do Android para o espaço 3D ou criar 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 subspaces e componentes espaciais

Ao desenvolver seu app para 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 é possível 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 MainActivity da mesma forma que você usa setContent. Um subspace no nível do app não tem limite de altura, largura e profundidade, fornecendo 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 do app entre XR e outros formatos sem precisar elevar o estado por toda a árvore de interface ou reprojetar o app.

Para mais informações, leia sobre como 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.

  • Observação sobre os modificadores de subespaço: preste atenção na ordem das APIs SubspaceModifier.
    • O deslocamento precisa ocorrer primeiro em uma cadeia de modificadores
    • Movível e redimensionável precisam ocorrer por último
    • A rotação precisa ser aplicada antes da escala.

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 exibir o conteúdo do app. Por exemplo, é possível exibir 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 abaixo.

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

  • Observação sobre os modificadores de subespaço: preste atenção à ordem das APIs SubspaceModifier.
    • O deslocamento precisa ocorrer primeiro em uma cadeia de modificadores.
    • Os modificadores móveis e redimensionáveis precisam ocorrer por último.
    • A rotação precisa ser aplicada antes da escala.
  • Como as APIs SpatialPanel são combináveis de subespaço, é necessário chamá-las dentro de Subspace ou setSubspaceContent. Chamar esse método fora de um subespaço gera uma exceção.
  • Permita que o usuário redimensione ou mova o painel adicionando SubspaceModifiers .movable ou .resizable.
  • Consulte nossas orientações de design de painel espacial para saber mais sobre dimensionamento e posicionamento. Consulte nossa documentação de referência para mais detalhes sobre a implementação do código.

Criar um orbitador

Um orbitador é um componente de interface espacial. Ele foi projetado para ser anexado a um painel espacial correspondente e contém itens de navegação e ação contextual relacionados a esse painel. Por exemplo, se você criou um painel espacial para exibir conteúdo de vídeo, pode adicionar controles de reprodução de vídeo dentro de um orbitador.

Exemplo de um orbitador

Conforme mostrado no exemplo abaixo, chame um orbitador dentro de um SpatialPanel para envolver controles do usuário, como 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

  • Observação sobre os modificadores de subespaço: preste atenção à ordem das APIs SubspaceModifier.
    • O deslocamento precisa ocorrer primeiro em uma cadeia de modificadores
    • Movível e redimensionável precisam ocorrer por último
    • A rotação precisa ser aplicada antes da escala.
  • 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 SpatialLayout 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

Usar 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

  • Observação sobre os modificadores de subespaço: preste atenção à ordem das APIs SubspaceModifier.
    • O deslocamento precisa ocorrer primeiro em uma cadeia de modificadores
    • Movível e redimensionável precisam ocorrer por último
    • A rotação precisa ser aplicada antes da escala.
  • Consulte Como adicionar conteúdo 3D para entender melhor como carregar conteúdo 3D em um volume.

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 ser empurrado ligeiramente para trás 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 z-depth 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 z do painel espacial, e o painel é empurrado para trás em 125dp quando a espacialização está ativada. O SpatialDialog ainda pode ser usado quando a espacialização não está ativada e 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.

Veja também