Fundamentos do Android XR (Parte 2): orbitadores e ambientes espaciais

1. Antes de começar

O que você aprenderá

  • As experiências de usuário exclusivas possibilitadas pelo Android XR.
  • Como otimizar um app para um headset Android XR usando a biblioteca Jetpack Compose XR.
  • Como usar elementos da interface da biblioteca Jetpack Compose XR.
  • Onde saber como criar aplicativos para Android XR.

O que este codelab não é

O que é necessário

O que você vai criar

Neste codelab, você vai otimizar ainda mais um app com alguns recursos atuais do XR, adicionando elementos da interface flutuantes e personalizando o ambiente virtual que envolve o usuário enquanto ele usa o app.

Ponto de partida

Resultado final

2. Começar a configuração

Acessar o código

  1. O código deste codelab pode ser encontrado no diretório xr-fundamentals do repositório xr-codelabs do GitHub. Para clonar o repositório, execute o seguinte comando:
git clone https://github.com/android/xr-codelabs.git
  1. Se preferir, baixe o repositório como um arquivo ZIP:

Abrir o projeto

  • Depois de iniciar o Android Studio, importe o diretório xr-fundamentals/part1. O diretório xr-fundamentals/part2 contém o código da solução, que você pode consultar a qualquer momento se tiver dificuldades ou apenas quiser conferir o projeto completo.

Conhecer o código

  • Depois de abrir o projeto no Android Studio, analise o código inicial.
  • Se você ainda não fez o primeiro codelab ou usou o emulador do Android XR, siga as etapas em Executar o app no emulador do Android XR.

3. Aprender os conceitos do XR: orbitadores

Os orbitadores são elementos da interface flutuantes disponíveis no Modo de Espaço Ampliado que geralmente são usados para controlar o conteúdo em painéis espaciais ou outras entidades em que a órbita está ancorada. O uso de orbitadores para controles de conteúdo dá mais espaço ao conteúdo, o que significa que os usuários podem acessar rapidamente os recursos contidos nos orbitadores enquanto o conteúdo principal permanece visível. Os orbitadores oferecem a versatilidade necessária para integrar componentes de interface atuais (como barras de navegação) ou criar novos.

Além disso, a API Orbiter permite renderizar o conteúdo de um orbitador onde ele normalmente seria executado no Modo de Espaço Compacto ou em um dispositivo não XR e o divide automaticamente em um orbitador quando executado no Modo de Espaço Ampliado.

No modo de Espaço Compacto, essa coluna de navegação é renderizada no painel principal do app.

No modo de Espaço Ampliado, a coluna de navegação é dividida em um orbitador anexado ao painel principal.

Nesse ponto, o app tem um botão na barra de apps na parte de cima para mudar entre o Modo de Espaço Compacto e o Modo de Espaço Ampliado. Esse botão é um exemplo perfeito de um controle que pode ser contido em um orbitador quando executado no modo de Espaço Ampliado. Mover o controle para orbitar o painel principal ajuda a destacar o controle e indica visualmente que ele vai reduzir o conteúdo do app para esse painel quando clicado.

O estado atual

O que você vai implementar

Para saber mais sobre as considerações de design para orbitadores, consulte Interface espacial.

4. Adicionar um orbitador

Unir o botão ativar/desativar do modo de espaço

Para transformar o botão do modo de espaço em um orbitador, una o elemento combinável ToggleSpaceModeButton em um elemento combinável Orbiter.

ui/component/XRFundamentalsTopAppBar .kt

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import androidx.xr.compose.spatial.EdgeOffset
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape

... 

Orbiter(
    position = OrbiterEdge.Top,
    alignment = Alignment.End,
    offset = EdgeOffset.inner(16.dp),
    shape = SpatialRoundedCornerShape(
        CornerSize(percent = 100)
    )
) {
    ToggleSpaceModeButton()
}

Agora, execute o app: ao executar no modo de Espaço Compacto, você vai notar que nada mudou. No entanto, quando você clica no botão e o app entra no modo de Espaço Ampliado, o botão não está mais localizado na barra de apps superior, mas na borda superior direita do painel espacial principal.

O orbitador é ancorado ao painel espacial principal porque é a entidade espacial pai mais próxima na árvore da UI. A posição exata do orbitador em relação ao painel espacial principal é determinada pelos parâmetros position, alignment e offset. Tente modificar esses parâmetros para conferir os comportamentos compatíveis.

Modo de Espaço Compacto

Modo de Espaço Ampliado

5. Aprender os conceitos do XR: ambientes espaciais

Usar um Orbiter para personalizar a posição no espaço 3D dos elementos da interface é uma ótima maneira de melhorar a experiência do usuário em dispositivos XR. Para melhorar ainda mais a experiência, personalize o ambiente espacial em que os usuários ficam quando usam o app.

Os ambientes espaciais podem incorporar recursos de profundidade, textura e geometria 3D para criar uma experiência visual rica e imersiva. Isso é feito usando uma imagem esférica skybox (no formato EXR) para fornecer um plano de fundo panorâmico distante e/ou um recurso de geometria (no formato glTF) para fornecer elementos em primeiro e segundo plano que podem ser mesclados em uma skybox. Por exemplo, um app de streaming de vídeo pode usar uma skybox noturna com um glTF de um cinema drive-in com uma tela de projeção e carros. Ao criar recursos para definir o ambiente espacial dos usuários, garanta que eles tenham resolução de alta qualidade e um tamanho de arquivo razoável. Consulte Otimizar recursos do ambiente para mais informações.

Além disso, a opacidade do ambiente espacial pode ser controlada. Isso permite transmitir vídeo do mundo real e o misturar ao ambiente virtual, o que pode ajudar os usuários a se orientarem

Uma pessoa em pé em um ambiente espacial rochoso com um grande painel de UI no meio do campo.

Na próxima etapa, você vai adicionar um recurso de geometria ao app e criar um menu para permitir que o usuário escolha o ambiente.

Para saber todos os detalhes sobre o design e a implementação de ambientes espaciais, consulte Ambientes espaciais e Adicionar ambientes espaciais ao app.

6. Permitir que os usuários mudem o ambiente espacial

Como os apps controlam o ambiente espacial

Antes de começar, é bom entender exatamente como os apps podem controlar o ambiente espacial.

Ao contrário do conteúdo nos painéis, os apps não controlam diretamente o ambiente. Em vez disso, eles podem interagir com a sessão do SceneCore para fornecer uma preferência para o ambiente que querem que o sistema use. Essa preferência é representada por uma SpatialEnvironmentPreference, que consiste em uma imagem EXR de skybox e/ou um gLTF de geometria. O que acontece quando o app fornece uma preferência depende dos recursos dele ao definir a preferência. Se o app tiver capacidade de mudar o ambiente, o sistema vai fazer isso imediatamente. Caso contrário, a preferência será aplicada quando o app ganhar esse recurso.

Por exemplo, os apps geralmente não conseguem mudar o ambiente enquanto são executados no Modo de Espaço Compacto, mas geralmente têm quando são executados no Modo de Espaço Ampliado. Se você permitir que um usuário defina uma preferência de ambiente no Modo de Espaço Compacto, essa preferência geralmente não vai entrar em vigor até que o app esteja em execução no Modo de Espaço Ampliado.

Adicionar uma dependência à biblioteca XR SceneCore

Para começar a modificar o ambiente espacial, adicione uma dependência à biblioteca XR SceneCore, que você vai usar para carregar os recursos do ambiente e definir as preferências dele. Também é necessário adicionar uma dependência ao artefato kotlinx-coroutines-guava, já que algumas APIs para carregar recursos usam o tipo de dados ListenableFuture.

libs.version.toml

[versions]
...
xrSceneCore = "1.0.0-alpha04"
kotlinxCoroutinesGuava = "1.10.2"

[libraries]
...
androidx-xr-scenecore = { group = "androidx.xr.scenecore", name = "scenecore", version.ref = "xrSceneCore"}
jetbrains-kotlinx-coroutines-guava = {group = "org.jetbrains.kotlinx", name="kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava"}

app/build.gradle.kts

dependencies {
    ...
    implementation(libs.androidx.xr.scenecore)
    implementation(libs.jetbrains.kotlinx.coroutines.guava)
    ...
}

Adicionar um recurso de ambiente ao projeto

Para especificar uma preferência de ambiente exclusiva, você vai precisar de um recurso de céu e/ou de geometria. Neste codelab, você vai usar apenas o recurso de geometria green_hills_ktx2_mipmap.glb, que pode ser encontrado na pasta part2 que contém o código da solução ou no GitHub (link em inglês).

  1. Clique com o botão direito do mouse no módulo do app na janela "Project" do Android Studio. Em seguida, selecione New > Folder > Assets Folder e clique em Finish para criar a pasta.
  2. Adicione o arquivo GLB à pasta app/src/main/assets que você acabou de criar.

Modelar as opções de ambiente

Para simplificar a interação entre o código da UI e as APIs do sistema, crie uma classe de dados do Kotlin para modelar cada opção de ambiente.

  1. Clique com o botão direito do mouse no pacote com.example.android.xrfundamentals na janela do projeto e selecione New > Package. Digite com.example.android.xrfundamentals.environment como o nome do pacote.
  2. Clique com o botão direito do mouse no pacote e selecione New > Kotlin Class/File. Insira EnvironmentOption como o nome e clique no tipo Data class.
  3. Adicione o código abaixo no arquivo que você acabou de criar:

EnvironmentOption.kt

data class EnvironmentOption(val name: String, val skyboxPath: String?, val geometryPath: String?)

val DEFAULT_ENVIRONMENT = EnvironmentOption("Default", null, null)

val ENVIRONMENT_OPTIONS = listOf(
    DEFAULT_ENVIRONMENT,
    EnvironmentOption("Green Hills", null, "green_hills_ktx2_mipmap.glb")
)

Adicionar um auxiliar para criar recursos de carregamento e retornar a SpatialEnvironmentPreference

Em seguida, adicione um método auxiliar à classe de dados para facilitar a conversão de uma EnvironmentOption na SpatialEnvrionmentPreference correspondente.

EnvironmentOption.kt

import androidx.xr.runtime.Session
import androidx.xr.scenecore.ExrImage
import androidx.xr.scenecore.GltfModel
import androidx.xr.scenecore.SpatialEnvironment
import kotlinx.coroutines.guava.await

...

data class EnvironmentOption(val name: String, val skyboxPath: String?, val geometryPath: String?) {
    suspend fun toSpatialEnvironmentPreference(session: Session): SpatialEnvironmentPreference? {
        if (skyboxPath == null && geometryPath == null) {
            return null
        } else {
            val skybox = skyboxPath?.let {
                ExrImage.create(session, it).await()
            }

            val geometry = geometryPath?.let {
                GltfModel.create(session, it).await()
            }

            return SpatialEnvironmentPreference(skybox, geometry)
        }
    }
}

Há algumas observações importantes:

  • Se a skybox e a geometria forem nulas, o valor "null" será retornado para indicar que a preferência de ambiente padrão do sistema deve ser usada. Consulte setSpatialEnvironmentPreference para mais informações.
  • Os recursos skybox e geometry são criados de forma assíncrona porque esses recursos geralmente são muito grandes e demoram para ser lidos na memória. Em um app de produção, convém armazenar esses recursos em cache na memória se você mudar os ambientes com frequência.

Implementar a UI de seleção do ambiente

Para implementar a UI, adicione um segundo orbitador que gire pelas opções de ambiente quando clicado.

Adicionar o orbitador

  1. Clique com o botão direito do mouse no módulo app na janela "Project" e selecione New > Vector Asset. Clique no campo Clip art, pesquise e selecione o recurso landscape (da família de ícones Filled), clique em OK e em Next para criar o recurso.
  2. Clique com o botão direito do mouse no pacote com.example.android.xrfundamentals.ui.component e selecione New > Kotlin Class/File. Insira EnvironmentSelectionOrbiter como o nome e clique no tipo File.
  3. No arquivo que você acabou de criar, adicione a implementação do elemento combinável EnvironmentSelectionOrbiter abaixo.

EnvironmentSelectionOrbiter.kt

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.xr.compose.spatial.EdgeOffset
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape
import com.example.android.xrfundamentals.R

@Composable
fun EnvironmentSelectionOrbiter(
    modifier: Modifier = Modifier,
    onClick: () -> Unit = {},
) {
    Orbiter(
        position = OrbiterEdge.Top,
        alignment = Alignment.Start,
        offset = EdgeOffset.inner(16.dp),
        shape = SpatialRoundedCornerShape(
            CornerSize(100)
        )
    ) {
        FilledTonalIconButton(
            modifier = modifier,
            onClick = onClick,
        ) {
            Icon(painterResource(R.drawable.baseline_landscape_24), "Show environment selection dialog")
        }
    }
}
  1. Por fim, adicione o EnvironmentSelectionOrbiter no painel espacial principal.

XRFundamentalsApp.kt

import androidx.xr.compose.platform.LocalSpatialCapabilities
import com.example.android.xrfundamentals.ui.component.EnvironmentSelectionOrbiter

...

SpatialPanel(...) {

    // Only show the environment selection orbiter if the app is actually able to
    // change the environment
    if (LocalSpatialCapabilities.current.isAppEnvironmentEnabled) {
        EnvironmentSelectionOrbiter(
            onClick = { TODO() }
        )
    }
    ...
}

Mudar o ambiente quando o orbitador for clicado

Para que tudo funcione, há uma última etapa, que é chamar setSpatialEnvironmentPreference no gerenciador de cliques do EnvironmentSelectionOrbiter.

  1. Configure uma variável para rastrear a opção de ambiente atual (fora do Subspace, para que o estado seja mantido ao alternar entre os modos de Espaço Compacto e Espaço Ampliado). Além disso, crie variáveis para a sessão atual de XR e um escopo de corrotina para chamar o auxiliar toSpatialEnvironmentPreference.

XRFundamentalsApp.kt

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.xr.compose.platform.LocalSession

... 

var currentEnvironmentOptionIndex by remember { mutableStateOf(0) }

Subspace {
    val session = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    ...
}
  1. Implemente o callback onClick para mudar entre as opções de ambiente.

XRFundamentalsApp.kt

EnvironmentSelectionOrbiter(
    onClick = {
        scope.launch {
            currentEnvironmentOptionIndex =
                (currentEnvironmentOptionIndex + 1) % ENVIRONMENT_OPTIONS.size
            session.scene.spatialEnvironment.setSpatialEnvironmentPreference(
                ENVIRONMENT_OPTIONS[currentEnvironmentOptionIndex].toSpatialEnvironmentPreference(
                    session
                )
            )
        }
    }
)

Execute o app mais uma vez e confira se você pode mudar entre "Green Hills" e os ambientes padrão.

b0e9571ef5f5597b.gif

7. Parabéns

Para continuar aprendendo sobre como aproveitar ao máximo o XR, confira os seguintes recursos e exercícios:

Leia mais

Desafios

  • Encontre ou crie outros recursos de ambiente e adicione-os como opções.
  • Modifique o controlador de ambiente e a interface para permitir que o usuário defina as preferências de passagem usando a API setPassthroughOpacityPreference. O controle de passagem é limitado por um recurso diferente do que a mudança dos recursos do ambiente.

Documentos de referência