Android XR

Criar experiências ancoradas no mundo para óculos XR com fio

Alto-falantes

Sobre

Sobre a palestra

Vá além do espaço da tela e ancore seus apps no mundo real com o Android XR. Nesta sessão, você vai aprender a criar experiências do Android XR com reconhecimento geoespacial usando o Jetpack Compose Glimmer e a API Geospatial do ARCore. Vamos começar explicando como conseguir a pose geoespacial de um dispositivo com o ARCore. Em seguida, você vai aprender como os componentes mínimos da interface do Glimmer podem ser ancorados em locais do mundo real usando o Sistema de Posicionamento Visual (VPS). Vamos combinar esses recursos para criar uma experiência espacial simples e com reconhecimento de local.

Resumo

  • O Android XR leva experiências digitais para ambientes físicos em uma variedade de dispositivos, desde óculos inteligentes até óculos XR com fio. Os apps 2D compatíveis com Android funcionam em headsets XR e óculos XR com fio com modificações mínimas.
  • O Glimmer do Jetpack Compose oferece componentes e estilos personalizados para telas ópticas transparentes. As funções auxiliares integradas ajustam as cores da marca para tons seguros para óculos, mantendo a visibilidade e o contraste em diferentes condições de iluminação.
  • A biblioteca Glimmer adiciona novos componentes, como paginadores e grupos de botões. Uma API de profundidade ajuda a separar elementos empilhados em telas transparentes em que o preto é transparente.
  • Os elementos de composição do Subspace colocam interfaces no espaço 3D e podem seguir âncoras do ARCore usando um alvo, como um QR code rastreado.
  • A API Geospatial no ARCore combina dados do Street View e medições de GPS para posicionar dispositivos com precisão em áreas compatíveis. É possível anexar componentes do Glimmer a âncoras geoespaciais reais usando coordenadas de consultas da API Places.

Recursos

Gemini Notebook

Tópicos

  • android-xr
  • jetpack-compose
  • glimmer
  • arcore
  • geospatial-api
  • realidade aumentada

Transcrição

Introdução ao Android XR

  • Olá, pessoal! Bem-vindos à primeira sessão sobre o Android XR na Conferência de Desenvolvedores Android.

  • Sim!

  • Estamos muito felizes em ver tantas pessoas aprendendo sobre o Android XR.

  • Meu nome é Yasmine Quintana Evjen, e estou aqui com Paul Hoover e Dereck Bridie, que vão participar da conversa.

  • O tema desta sessão é o Android XR e como você pode criar experiências ancoradas no espaço e no mundo real com o Jetpack Compose Glimmer e o ARCore para Jetpack XR.

  • Mas primeiro, vamos fazer uma breve introdução ao Android XR.

  • O Android XR é uma nova plataforma que redefine a forma como os usuários interagem com seu app, indo além da tela e entrando no espaço ao redor deles.

  • Projetamos a plataforma para levar experiências digitais diretamente ao ambiente físico.

  • Isso pode significar sobreposições de fácil visualização que melhoram a presença no mundo real ou um ambiente imersivo infinito.

  • É o mesmo ecossistema Android aberto que estamos construindo juntos há anos, mas agora a tela é do tamanho da sala e tão ampla quanto sua imaginação permitir.

  • Vemos a XR como um espectro de dispositivos que vão desde óculos inteligentes, que permitem experiências leves e aumentadas em óculos de áudio e tela, até headsets imersivos e óculos XR com fio, que oferecem foco, produtividade e recursos de entretenimento imersivos.

  • O dispositivo mais recente da família Android XR é o XREAL AURA, que já está disponível para compra.

  • Esse dispositivo é leve e potente, permitindo experiências de XR com a familiaridade do Android.

  • Nossa visão para a plataforma é que, se você já desenvolve para Android, está pronto para desenvolver para XR.

  • Usando o mesmo modelo de app, biblioteca e ferramentas que você já conhece, queremos simplificar o desenvolvimento no Android XR.

  • Com poucas ou nenhuma modificação, seu app 2D compatível com Android vai funcionar em headsets Android XR e óculos XR com fio.

SDK do Jetpack para XR e ferramentas para desenvolvedores

  • Para criar experiências de XR incríveis que parecem naturais, oferecemos um conjunto de bibliotecas criadas especialmente para a plataforma XR.

  • Para desenvolvedores acostumados a criar experiências em outras plataformas imersivas, é possível usar o OpenXR e nosso suporte a mecanismos 3D, como Unity, Godot e Unreal.

  • Vamos falar sobre como criar com bibliotecas Android.

  • Oferecemos o SDK do Jetpack para XR, um conjunto de bibliotecas Kotlin que ajuda a estender seus apps para XR em experiências imersivas e de realidade aumentada.

  • Vamos dar uma olhada nas nossas bibliotecas imersivas.

  • Nossas bibliotecas do Jetpack XR chegaram às versões Beta e estável este ano, oferecendo a confiança necessária para você começar a criar experiências imersivas de XR.

  • A camada de percepção é o ARCore para Jetpack XR.

  • Essa biblioteca fornece informações sobre o mundo real, como onde estão as superfícies planas, como uma mesa, e como as mãos dos usuários estão posicionadas.

  • Ele pode rastrear pontos no espaço 3D e fornecer essas informações aos apps quando as permissões corretas são concedidas.

  • Depois, há o Jetpack XR SceneCore, que fornece um gráfico de cena para posicionar seu conteúdo espacial.

  • Essa biblioteca é capaz de apresentar entidades no espaço 3D e oferece comportamentos espaciais para posicionamento preciso do gráfico de cena.

  • O Jetpack Compose para XR oferece novos elementos combináveis de subespaço criados especialmente para 3D.

  • Os desenvolvedores Android que já conhecem os layouts do Compose, como a criação com linhas e colunas, vão achar intuitivo o layout da interface espacial.

  • Ao criar uma linha espacial, você pode organizar de forma declarativa vários painéis espaciais lado a lado sem precisar calcular manualmente a posição ou o tamanho deles.

  • Usando elementos combináveis de subespaço, como um painel espacial ou um orbiter, você pode estender facilmente seus componentes 2D para esse novo espaço 3D.

  • E, claro, os elementos combináveis de subespaço são capazes de fazer mais do que apenas mostrar conteúdo 2D.

  • O Compose para XR oferece suporte a vídeos espaciais e modelos 3D usando a superfície externa espacial e o modelo glTF espacial.

Como criar uma interface com o Glimmer do Jetpack Compose

  • Agora, vamos falar sobre apps aumentados.

  • Com a biblioteca Jetpack Projected, você pode aprimorar seu app para dispositivos móveis para usar hardware de óculos inteligentes, como sensores e tela.

  • Por fim, o Jetpack Compose Glimmer, um kit de ferramentas de interface para criar experiências do Android XR otimizadas para óculos.

  • O Jetpack Compose Glimmer consiste em estilos e componentes prontos para uso na criação de experiências de XR.

  • Lançamos o Glimmer no ano passado e continuamos expandindo os recursos, incluindo uma habilidade de agente para ajudar você a criar apps de óculos de tela com o Glimmer.

    android skills add display-glasses-with-jetpack-compose-glimmer
  • Desde o lançamento, estamos ouvindo seu feedback.

  • Sabemos que vocês queriam que o Jetpack Compose Glimmer fosse além dos óculos inteligentes.

  • Temos o prazer de anunciar que fizemos mudanças para melhorar as experiências ancoradas espacialmente com o Glimmer, que vamos detalhar mais adiante nesta palestra.

  • Nosso trabalho para otimizar o Glimmer começou com a criação de uma nova superfície para exibição em vários formatos de XR, de uma tela a óculos XR com fio.

  • A adição dessa nova superfície oferece mais flexibilidade para personalizar sua interface.

  • E para aumentar a diferenciação da marca do seu app, adicionamos cores personalizadas aos componentes do Glimmer.

  • Use-os para expressar o estilo da sua marca nos apps de XR.

  • Nossa biblioteca Glimmer do Jetpack Compose oferece funções auxiliares que convertem as cores de destino e da marca em cores seguras para óculos.

  • Isso garante a visibilidade em telas ópticas transparentes.

  • Vamos ver isso em ação.

  • Queremos mudar a cor do botão em foco para este app de óculos. Vou selecionar uma das minhas cores favoritas.

    val brandColor = Color(0xFFFFAABB)
    val focusedColor = ButtonDefaults.focusedColor(brandColor)
    
    Column(…) {
       LeadingImageCard(
           focusable = false,
           …
       ) {
           …
       }
       ButtonGroup(...) {
           Button(
               onClick = {},
               leadingIcon = { … },
               focusedColor = focusedColor,
           ) { Text("Add to cart") }
    
           /* Rest of the buttons */
       }
    }
  • Observe que, quando a cor é adicionada ao código, as funções auxiliares do Glimmer a ajustam para uma cor segura para óculos, otimizando a visibilidade.

  • Tudo isso é criado na nossa biblioteca Glimmer do Jetpack Compose, garantindo que você crie experiências que fiquem ótimas em vários ambientes.

  • No Android Studio, você pode usar o emulador para visualizar como a interface dos óculos vai ficar em vários planos de fundo, como ambientes claros, escuros ou movimentados.

  • Essas atualizações são apenas o começo.

  • Não.

  • Minha equipe me disse que ninguém ia rir dessa, mas eu insisti.

  • Agora, vou passar a palavra para o Paul, que vai falar mais sobre o Jetpack Compose Glimmer.

Princípios de design de interface para óculos com tela

  • Testando, tudo certo!

  • Preciso clicar no botão "Próximo".

  • Certo, obrigada, Yasmine.

  • Certo.

  • Como a Yasmine disse, o Jetpack Compose Glimmer oferece os elementos básicos para desenvolver aplicativos adaptados para óculos com tela.

  • Nossos componentes integram as práticas recomendadas para telas ópticas transparentes e foram criados diretamente nas APIs para que vocês, desenvolvedores, possam criar experiências ópticas transparentes bonitas e funcionais.

  • Ao projetar e criar apps para dispositivos ópticos transparentes como este, é preciso ter algumas coisas em mente.

  • Primeiro, é uma tela transparente, ou seja, não há preto.

  • Quando o mundo está visível, o preto é transparente.

  • Portanto, não é possível contar com planos de fundo escuros para manter o contraste da interface, do texto e do conteúdo.

  • Mesmo que o preto seja invisível, você pode usá-lo para ocultar outros conteúdos digitais.

  • No framework de interface do Glimmer, temos uma API de profundidade que permite separar facilmente os elementos da interface e fornecer uma sensação de hierarquia.

  • Aqui, você pode ver como a profundidade nos cards dessa pilha ajuda a mantê-los separados uns dos outros.

  • Nossos cientistas de visão forneceram proporções de contraste ideais para textos e ícones que são visíveis na maioria das condições de iluminação.

  • Como a Yasmine mostrou, criamos isso ou incorporamos isso ao framework para que, ao personalizar as cores da superfície, você tenha certeza de que sua interface será legível para os usuários.

  • Incorporamos essas e outras práticas recomendadas à nossa biblioteca de componentes em expansão.

  • Lançamos muitos componentes novos recentemente.

  • Uma das nossas novidades é o componente de paginação com transições de movimento e gestos integrados.

    val pagerState =
      rememberGlimmerPagerState(pageCount = { 10 })
    
    GlimmerHorizontalPager(pagerState) { page ->
       /* Card Content */
    }
  • Se você já conhece o pager horizontal do Compose, esse componente vai ser muito familiar.

  • Também lançamos quatro novas variantes de cards projetadas para integração perfeita com pagers, pilhas e outros elementos da interface do Glimmer.

  • Os cards de imagem à esquerda e à direita mostram texto e imagens lado a lado.

  • Portanto, use essas variantes de card quando tiver uma quantidade igual de texto e informações visuais.

  • Separamos o card de imagem em uma variante própria.

  • É melhor usar esse layout quando você tem uma imagem grande com uma única linha de texto ou legenda, como este seletor de vídeo.

  • A variante de card de ação incorpora um único botão a um card.

  • Use esse recurso quando quiser que os usuários saibam exatamente o que vai acontecer ao ativar o card, como neste seletor de destino.

  • Os grupos de botões permitem que você coloque muitos botões em um espaço pequeno.

    ButtonGroup {
       Button(onClick = {}) { Text("Button 1") }
       Button(onClick = {}) { Text("Button 2") }
       Button(onClick = {}) { Text("Button 3") }
       Button(onClick = {}) { Text("Button 4") }
       Button(onClick = {}) { Text("Button 5") }
    }
  • Com esse código simples, você tem transições bonitas, comportamentos de rolagem, scrims que ocultam as bordas e aparecem apenas quando necessário, além de entradas que são roteadas automaticamente dos óculos para o controle.

  • Os grupos de botões também podem conter nossos novos botões de ícone e de ativar/desativar.

    var buttonChecked by remember { mutableStateOf(false) }
    
    ToggleButton(
      checked = buttonChecked,
      leadingIcon = { Icon(FavoriteIcon) },
      onCheckedChange = { buttonChecked = it }
    ) {
      Text(if (buttonChecked) "On" else "Off")
    }
  • Você pode ver todos os nossos tipos de botão aqui em uso para criar esse controle de mídia.

  • E agora é fácil testar sua interface em diferentes planos de fundo.

  • O emulador de óculos permite colocar fotos em 360° e girar para testar em diferentes planos de fundo, planos de fundo movimentados, claros, escuros.

  • Os grupos de botões são ótimos para usar em uma visualização detalhada como essa para mostrar ações contextuais.

  • Ao tocar, você pode usar transições de elementos compartilhados para navegar até um estado de visualização detalhado e, em seguida, usar o gesto de voltar do sistema para retornar à visualização anterior.

  • Muito bom.

  • Além disso, adicionamos um indicador de entrada de voz e caixas de diálogo de alerta.

  • Então, sim, muitas novidades.

  • Agora que já vimos algumas das novidades, vamos criar algo.

Visão geral das âncoras baseadas em localização

  • Além de usar o Glimmer para criar óculos inteligentes, podemos usá-lo para criar experiências para óculos XR com fio.

  • Queremos que você crie experiências que ampliem o mundo ao seu redor, começando com âncoras baseadas em locais do mundo real.

  • Imagine que você está em uma cidade nova, talvez viajando para uma conferência de desenvolvedores, e quer encontrar um lugar para comer.

  • Você pode pedir ao seu app de navegação favorito para encontrar restaurantes próximos e as avaliações deles.

  • As informações podem ser sobrepostas diretamente no local, fornecendo contexto, inteligência e orientação.

  • Aqui, você pode ver nosso componente mais recente do Glimmer, chamado Marcador de Ícone.

  • Ele permite destacar elementos importantes no mundo físico e combiná-los com outros componentes do Glimmer, como este card.

  • Neste exemplo, ele anota pontos de interesse usando as APIs Google Maps, Places e Geospatial.

  • Ei, Dereck, vamos mostrar como configurar isso.

  • Vamos lá!

  • Certo.

  • Certo.

  • Como o Paul mencionou, vamos começar a criar.

  • Então, vamos usar a documentação para desenvolvedores em developer.android.com/xr e dar uma olhada na biblioteca de componentes do Jetpack Compose Glimmer.

  • Assim, você vai encontrar uma visão geral de todos os componentes disponíveis no Glimmer.

  • Vamos começar com essa parte da interface que o Paul mostrou antes.

  • Por isso, vamos usar a interface do Glimmer para criar um indicador espacial e uma caixa de informações.

  • Vamos começar identificando os componentes que serão usados.

  • À esquerda, você verá um marcador de ícone e, à direita, usaremos um card que descreve o lugar.

  • Se você acessar o Android Studio, poderá visualizar esse painel como qualquer outro componente do Compose e conferir as mudanças em tempo real.

  • Como mencionado antes, é possível visualizar a interface em todos os tipos de ambientes de plano de fundo e garantir que o contraste da interface seja suficiente em vários tipos de cenários, como ambientes escuros e claros.

  • Agora que a tela foi definida, vamos criar uma linha, colocar um marcador de ícone nela com algum espaçamento e um card com um texto de exemplo.

  • E pronto.

  • Se você estiver satisfeito com a prévia em 2D, podemos começar a marcar algo no mundo real, e faremos isso espacializando a interface.

  • Isso significa que vamos fazer o layout em 3D em vez de 2D.

  • Portanto, as interfaces espaciais estão contidas em subespaços, que são espaços 3D na frente do usuário.

    Subspace {
      SpatialPanel {
        VideoPlayer()
      }
    }
  • Por padrão, um subespaço organiza o conteúdo a uma distância confortável do usuário.

  • Antes de mostrar como usar a API Geospatial, vamos colocar algo por perto.

  • O elemento combinável de subespaço que mencionei antes tem um parâmetro opcional que permite especificar um alvo para seguir.

    val state by
      qrCode.state.collectAsState()
    Subspace(FollowTarget.anchor(anchor)) {
      SpatialPanel {
        Row {
          IndicatorDot()
          Spacer()
          QrCodeCard(state)
        }
      }
    }
  • Para fixar algo no mundo real, primeiro você precisa criar uma âncora do ARCore para Jetpack XR.

  • Neste exemplo, estou usando o recurso de reconhecimento de QR code.

  • Isso vai permitir que você leia e rastreie QR codes para que seu conteúdo possa seguir esse QR code no espaço, e vamos anexar a interface que vimos anteriormente.

Integração da API Geospatial com a Plataforma Google Maps

  • Ok.

  • Mostrei como colocar conteúdo digital no mundo real por perto.

  • Vamos voltar ao exemplo do Paulo.

  • Eu sou dos Países Baixos.

  • Não conheço muito bem Mountain View.

  • Por isso, depois deste evento, quero ajudar você a criar um app para encontrar cafeterias legais.

  • Vamos ver como podemos ajudar nossos usuários a encontrar alguns com a API Geospatial, que agora está localizada no ARCore para Jetpack XR.

  • Por isso, a API Geospatial, que foi lançada originalmente para smartphones, agora também está disponível no Android XR.

  • Ele usa dados do Google Street View e os combina com medições de GPS para posicionar o dispositivo em áreas compatíveis.

  • Assim, em óculos inteligentes, a API Geospatial pode ser usada para determinar com precisão o local e a direção do usuário, além de permitir casos de uso de navegação guiada de alta precisão.

  • Em dispositivos imersivos, como óculos XR com fio, a API Geospatial permite criar experiências posicionadas com precisão no mundo real.

  • Por exemplo, é possível fixar conteúdo espacial em edifícios ou criar passeios especializados pela cidade.

  • Vamos começar a usar a API Geospatial com estas três etapas.

  • A primeira é que você vai criar um projeto do Google Cloud Platform e configurar a autenticação no seu app.

  • Em seguida, quando a API Geospatial estiver configurada e ativada no seu app, peça permissão ao usuário para recuperar a localização dele usando a granularidade fina.

  • Depois de configurar a sessão para usar a API Geospatial, está tudo pronto.

  • Você encontra todos os detalhes sobre a documentação para desenvolvedores da API Geospatial na nossa página recém-atualizada para desenvolvedores do Android XR.

  • Vamos começar usando a localização do usuário para pedir à API Places da Plataforma Google Maps que encontre cafeterias legais perto dele.

  • Primeiro, onde está o usuário?

  • Vamos pedir à API Geospatial para receber atualizações contínuas sobre a localização do usuário.

    produceState<AppStatus>(Initializing) {
      val geospatial = Geospatial.getInstance(session)
    
      val wait = geospatial.state.onEach {
         value = WaitingForGeospatial(it.geospatialTrackingState)
      }.first {
         it.geospatialTrackingState == Geospatial.GeospatialTrackingState.RUNNING
      }
    
      val accuracyWait = geospatial.state.onEach {
         value = WaitingForGeospatialAccuracy(it)
      }.first { it.horizontalAccuracy < horizontalAccuracyRequirement }
    
      value = WaitingForPlacesResults
      val result = client.findNearby(context, accWait.geospatialPose)
      value = Done(result)
    
    }
  • Portanto, vamos acessar o objeto geoespacial pela sessão de XR.

  • Depois, vamos esperar que o Geospatial seja inicializado e comece a funcionar, enquanto ele ativa os sensores necessários e se conecta às APIs do Cloud.

  • Em seguida, vamos esperar um certo nível de acurácia.

  • Portanto, os níveis de precisão podem melhorar enquanto as medições de GPS são recebidas ou quando o sistema de posicionamento visual encontra correspondências no local do usuário.

  • Aguardar um alto nível de precisão é importante para garantir que as experiências de localização sejam ancoradas no lugar certo e que os resultados sejam da área próxima ao usuário.

  • Por fim, estamos prontos para realizar a pesquisa com a API Places usando os valores de local da API Geospatial.

    val bounds = CircularBounds.newInstance(
      LatLng(pose.latitude, pose.longitude),
      100.0, // radius, in meters
    )
    val placeFields = listOf(
      Place.Field.ID, Place.Field.DISPLAY_NAME,
      Place.Field.RATING, Place.Field.LOCATION,
    )
    return placesClient.searchNearby(
      SearchNearbyRequest.builder(bounds, placeFields)
        .setIncludedTypes(listOf(PlaceTypes.CAFE))
        .setMaxResultCount(10)
        .build()
    ).await()
  • Portanto, vamos usar um SearchNearbyRequest com a API Places para consultar lugares ao redor da posição do usuário que obtivemos antes e encontrar resultados em um raio de 100 m.

  • Vamos receber o ID do lugar, o nome de exibição, a avaliação e, claro, o local.

  • Por fim, vamos realizar a pesquisa com os parâmetros especificados, incluindo um filtro de tipo de lugar.

  • Isso é tudo sobre os dados. Vamos voltar para a interface.

  • Então, precisamos descobrir como colocar o marcador e a caixa de informações que criamos antes. Para isso, vamos usar âncoras geoespaciais.

  • Voltando ao código, vamos acessar a API Geospatial usando nossa sessão de XR e criar uma âncora geoespacial usando uma latitude e longitude específicas que foram obtidas nos resultados da pesquisa anteriormente.

    val anchorResult by produceState<AnchorResult?>(null) {
     val geospatial = Geospatial.getInstance(session)
     value = geospatial.createAnchorOnSurface(
       place.location.latitude, place.location.longitude,
       surface = GeospatialSurface.TERRAIN,
       altitudeAboveSurface = 3.0,
       eastUpSouthQuaternion = Quaternion()
     )
    }
  • Neste exemplo, especificamos que a altitude deve ser de 3 m acima do terreno nessas coordenadas, mas você também pode fixar em telhados compatíveis, o que significa que o valor da altitude será determinado em relação ao telhado do edifício naquele local.

  • Também é possível girar a âncora usando um quatérnio, que especifica os valores de rotação da âncora.

  • Mas vamos usar os padrões neste exemplo porque faremos a interface girar para ficar de frente para o usuário.

  • Em seguida, vamos anexar a interface do Glimmer de antes a essa âncora, o que realmente fornece os elementos básicos para uma experiência ancorada espacialmente.

    when (anchorResult) {
     is AnchorCreateSuccess ->
       Subspace(FollowTarget.anchor(anchorResult.anchor)) {
         SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
           PlaceIndicator(place)
         }
       }
    
     else ->
       Log.e("Demo", "Failed to create anchor $anchorResult")
    }
  • Por isso, acreditamos que a API Geospatial pode proporcionar ótimas experiências de navegação, e o design com o Glimmer vai ajudar a criar experiências de usuário fáceis de entender, feitas especialmente para suas telas transparentes.

  • Para mais informações sobre a API Geospatial, acesse o site developer.android.com/xr, que foi atualizado recentemente.

  • Agora, vou passar a palavra para Yasmine para encerrar.

Conclusão e recursos para desenvolvedores

  • Nesta sessão, analisamos alguns recursos do Android XR: ARCore para Jetpack XR e Jetpack Compose Glimmer.

  • Estamos animados com os novos recursos que o Android XR vai trazer para os óculos XR com fio.

  • Os desenvolvedores já estão criando para o Android XR, e esperamos ver seu trabalho criativo na Google Play Store.

  • Para começar, confira nossa documentação para desenvolvedores, inspire-se com nossos experimentos de XR e comece a usar os codelabs.

  • Temos várias sessões, demonstrações e workshops incríveis de XR planejados para vocês.

  • Então, fique por dentro e aprenda a criar para o Android XR em experiências imersivas e de realidade aumentada.

  • Em nome da equipe do Android XR, agradecemos a presença de todos e esperamos que vocês aproveitem o restante da Conferência de Desenvolvedores Android.

  • Então, obrigado.

Código

Snippets de código

Snippet de shell em 04:39 em "Como criar uma interface com o Jetpack Compose Glimmer"
android skills add display-glasses-with-jetpack-compose-glimmer
Snippet do Kotlin em 05:49 em "Como criar uma interface com o Jetpack Compose Glimmer"
val brandColor = Color(0xFFFFAABB)
val focusedColor = ButtonDefaults.focusedColor(brandColor)

Column(…) {
   LeadingImageCard(
       focusable = false,
       …
   ) {
       …
   }
   ButtonGroup(...) {
       Button(
           onClick = {},
           leadingIcon = { … },
           focusedColor = focusedColor,
       ) { Text("Add to cart") }

       /* Rest of the buttons */
   }
}
Snippet em Kotlin às 08:37 em "Princípios de design de interface para óculos com tela"
val pagerState =
  rememberGlimmerPagerState(pageCount = { 10 })

GlimmerHorizontalPager(pagerState) { page ->
   /* Card Content */
}
Snippet do Kotlin em 09:50 em "Princípios de design de interface para óculos com tela"
ButtonGroup {
   Button(onClick = {}) { Text("Button 1") }
   Button(onClick = {}) { Text("Button 2") }
   Button(onClick = {}) { Text("Button 3") }
   Button(onClick = {}) { Text("Button 4") }
   Button(onClick = {}) { Text("Button 5") }
}
Snippet do Kotlin às 10:13 em "Princípios de design de interface para óculos com tela"
var buttonChecked by remember { mutableStateOf(false) }

ToggleButton(
  checked = buttonChecked,
  leadingIcon = { Icon(FavoriteIcon) },
  onCheckedChange = { buttonChecked = it }
) {
  Text(if (buttonChecked) "On" else "Off")
}
Snippet do Kotlin em 13:55 em "Visão geral das âncoras baseadas em localização"
Subspace {
  SpatialPanel {
    VideoPlayer()
  }
}
Snippet do Kotlin aos 14:16 em "Visão geral das âncoras baseadas em localização"
val state by
  qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
  SpatialPanel {
    Row {
      IndicatorDot()
      Spacer()
      QrCodeCard(state)
    }
  }
}
Snippet do Kotlin aos 16:43 em "Integração da API Geospatial e da Plataforma Google Maps"
produceState<AppStatus>(Initializing) {
  val geospatial = Geospatial.getInstance(session)

  val wait = geospatial.state.onEach {
     value = WaitingForGeospatial(it.geospatialTrackingState)
  }.first {
     it.geospatialTrackingState == Geospatial.GeospatialTrackingState.RUNNING
  }

  val accuracyWait = geospatial.state.onEach {
     value = WaitingForGeospatialAccuracy(it)
  }.first { it.horizontalAccuracy < horizontalAccuracyRequirement }

  value = WaitingForPlacesResults
  val result = client.findNearby(context, accWait.geospatialPose)
  value = Done(result)

}
Snippet do Kotlin em 17:27 em "Integração da API Geospatial e da Plataforma Google Maps"
val bounds = CircularBounds.newInstance(
  LatLng(pose.latitude, pose.longitude),
  100.0, // radius, in meters
)
val placeFields = listOf(
  Place.Field.ID, Place.Field.DISPLAY_NAME,
  Place.Field.RATING, Place.Field.LOCATION,
)
return placesClient.searchNearby(
  SearchNearbyRequest.builder(bounds, placeFields)
    .setIncludedTypes(listOf(PlaceTypes.CAFE))
    .setMaxResultCount(10)
    .build()
).await()
Snippet do Kotlin em 18:09 em "Integração da API Geospatial e da Plataforma Google Maps"
val anchorResult by produceState<AnchorResult?>(null) {
 val geospatial = Geospatial.getInstance(session)
 value = geospatial.createAnchorOnSurface(
   place.location.latitude, place.location.longitude,
   surface = GeospatialSurface.TERRAIN,
   altitudeAboveSurface = 3.0,
   eastUpSouthQuaternion = Quaternion()
 )
}
Snippet do Kotlin aos 18:53 em "Integração da API Geospatial e da Plataforma Google Maps"
when (anchorResult) {
 is AnchorCreateSuccess ->
   Subspace(FollowTarget.anchor(anchorResult.anchor)) {
     SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
       PlaceIndicator(place)
     }
   }

 else ->
   Log.e("Demo", "Failed to create anchor $anchorResult")
}