Android XR을 사용하면 화면 공간을 넘어 실제 세계에 앱을 고정할 수 있습니다. 이 세션에서는 Jetpack Compose Glimmer와 ARCore의 Geospatial API를 사용하여 지리 공간 인식 Android XR 환경을 빌드하는 방법을 알아봅니다. 먼저 ARCore를 사용하여 기기의 지리 공간적 포즈를 가져오는 방법을 살펴보겠습니다. 그런 다음 Glimmer의 최소 UI 구성요소를 시각적 위치 확인 시스템 (VPS)을 통해 실제 위치에 고정하는 방법을 알아봅니다. 이러한 요소를 결합하여 간단한 위치 인식 공간 환경을 빌드해 보겠습니다.
스크립트
안녕하세요. Android 개발자 서밋의 Android XR 트랙 첫 번째 세션에 오신 것을 환영합니다.
정답입니다.
많은 분이 Android XR에 대해 알아보고 계셔서 정말 기쁩니다.
저는 야스민 퀸타나 에브젠입니다. 폴 후버와 데렉 브라이디도 함께하고 있으며, 대화가 진행되는 동안 함께할 예정입니다.
이번 세션의 주제는 Android XR과 Jetpack Compose Glimmer 및 Jetpack XR용 ARCore를 사용하여 공간에 고정된 실제 환경을 빌드하는 방법입니다.
먼저 Android XR을 간단히 소개하겠습니다.
Android XR은 사용자가 앱과 상호작용하는 방식을 재정의하는 새로운 플랫폼으로, 화면을 넘어 주변 공간으로 확장됩니다.
Google은 디지털 경험을 물리적 환경에 직접 가져오도록 플랫폼을 설계합니다.
실제 환경을 개선하는 한눈에 볼 수 있는 오버레이든, 무한한 몰입형 환경이든 상관없습니다.
수년 동안 함께 구축해 온 개방형 Android 생태계가 이제 방만큼 크고 상상력만큼 넓은 캔버스로 확장되었습니다.
Google은 XR을 오디오 및 디스플레이 글라스에서 가볍고 증강된 경험을 제공하는 지능형 글라스부터 집중력, 생산성, 몰입형 엔터테인먼트 기능을 제공하는 몰입형 헤드셋과 유선 XR 글라스에 이르는 다양한 기기로 보고 있습니다.
Android XR 제품군의 최신 기기인 XREAL AURA는 현재 구매할 수 있습니다.
이 기기는 가볍고 강력하며 Android의 익숙함을 바탕으로 XR 환경을 지원합니다.
이 플랫폼의 비전은 이미 Android용으로 개발하고 있다면 XR용으로 개발할 준비가 된 것입니다.
Google은 개발자가 익숙한 앱 모델, 라이브러리, 도구를 사용하여 Android XR에서 간편하게 빌드할 수 있도록 지원하고자 합니다.
기존의 2D Android 호환 앱은 수정 없이 또는 약간의 수정만으로 Android XR 헤드셋과 유선 XR 글라스에서 작동합니다.
또한 친숙한 느낌의 멋진 XR 환경을 만들 수 있도록 XR 플랫폼용으로 특별히 빌드된 라이브러리 집합을 제공합니다.
다른 몰입형 플랫폼에서 환경을 빌드하는 데 익숙한 개발자는 OpenXR과 Unity, Godot, Unreal과 같은 3D 엔진 지원을 사용하여 빌드할 수 있습니다.
Android 라이브러리를 사용한 빌드에 관해 알아보겠습니다.
Google은 몰입형 및 증강 환경에서 앱을 XR로 확장하는 데 도움이 되는 Kotlin 라이브러리 세트인 Jetpack XR SDK를 제공합니다.
몰입형 라이브러리를 살펴보겠습니다.
올해 Jetpack XR 라이브러리가 베타 및 안정화 버전에 도달하여 몰입형 XR 환경을 빌드하는 데 필요한 자신감을 얻을 수 있습니다.
인식 레이어는 Jetpack XR용 ARCore입니다.
이 라이브러리는 테이블과 같은 평평한 표면의 위치, 사용자의 손 위치 등 실제 세계에 관한 정보를 제공합니다.
3D 공간에서 포인트를 추적할 수 있으며, 적절한 권한이 부여되면 해당 정보를 앱에 제공합니다.
Jetpack XR SceneCore는 공간 콘텐츠를 배치하기 위한 장면 그래프를 제공합니다.
이 라이브러리는 3D 공간에 항목을 표시할 수 있으며, 정확한 장면 그래프 배치를 위한 공간 동작을 제공합니다.
XR용 Jetpack Compose는 3D용으로 특별히 빌드된 새로운 하위 공간 컴포저블을 제공합니다.
행과 열을 사용한 빌드와 같은 Compose 레이아웃에 이미 익숙한 Android 개발자는 공간 UI를 배치하는 것이 직관적이라고 느낄 것입니다.
공간 행을 만들면 위치나 크기를 수동으로 계산하지 않고도 여러 공간 패널을 나란히 선언적으로 배치할 수 있습니다.
공간 패널이나 오비터와 같은 하위 공간 컴포저블을 사용하면 2D 구성요소를 이 새로운 3D 공간으로 쉽게 확장할 수 있습니다.
물론 subspace 컴포저블은 2D 콘텐츠를 표시하는 것 이상의 기능을 제공합니다.
XR용 Compose는 공간 외부 표면과 공간 glTF 모델을 통해 3D 모델뿐만 아니라 공간 동영상도 지원합니다.
이제 증강 앱에 대해 알아보겠습니다.
Jetpack Projected 라이브러리를 사용하면 기존 모바일 앱을 개선하여 센서 및 디스플레이와 같은 지능형 안경 하드웨어를 활용할 수 있습니다.
마지막으로, 안경에 최적화된 Android XR 환경을 빌드하기 위한 UI 도구 키트인 Jetpack Compose Glimmer가 있습니다.
Jetpack Compose Glimmer는 XR 환경을 빌드하기 위한 스타일과 바로 사용할 수 있는 구성요소로 구성됩니다.
Google은 작년에 Glimmer를 출시했으며, Glimmer를 사용하여 디스플레이 안경 앱을 빌드하는 데 도움이 되는 에이전트형 기술을 비롯한 기능을 계속 확장해 왔습니다.
android skills add display-glasses-with-jetpack-compose-glimmer출시 이후 Google은 사용자의 의견을 수렴해 왔습니다.
Jetpack Compose Glimmer를 지능형 글라스 외에도 확장해 달라는 의견이 있었습니다.
Glimmer를 통해 공간 고정 환경을 더 효과적으로 지원하기 위해 변경사항을 적용했습니다. 이 내용은 이 세션의 후반부에서 자세히 살펴보겠습니다.
Glimmer를 최적화하는 작업은 디스플레이부터 유선 XR 글라스에 이르기까지 다양한 XR 폼 팩터에 표시할 수 있는 새로운 표시 경로를 만드는 것에서 시작되었습니다.
이 새로운 표시 경로가 추가되어 UI를 맞춤설정하는 방법이 더 유연해졌습니다.
앱 브랜드의 차별성을 높이기 위해 Glimmer 구성요소에 맞춤 색상을 추가했습니다.
이를 사용하여 XR 앱 전반에서 브랜드 스타일을 표현하세요.
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 */
}
}코드에 색상이 추가되면 Glimmer 도우미 함수가 색상을 안경에 적합한 색상으로 조정하여 가시성을 최적화합니다.
이 모든 기능은 Jetpack Compose Glimmer 라이브러리 내에 빌드되어 다양한 환경에서 멋지게 보이는 환경을 빌드할 수 있습니다.
Android 스튜디오에서는 에뮬레이터를 사용하여 밝거나 어둡거나 복잡한 환경 등 다양한 배경에서 스마트 안경 UI가 어떻게 표시되는지 시각화할 수 있습니다.
이러한 업데이트는 시작에 불과합니다.
아니요.
팀원들은 이 농담이 재미없을 거라고 했지만 저는 여전히 이 농담을 좋아합니다.
이제 폴이 Jetpack Compose Glimmer에 관해 자세히 설명해 드리겠습니다.
테스트 중입니다.
다음 버튼을 눌러야 합니다.
좋습니다. 야스민, 감사합니다.
좋습니다.
야스민이 말했듯이 Jetpack Compose Glimmer는 디스플레이 안경에 맞춤설정된 애플리케이션을 개발하기 위한 기본 구성 요소를 제공합니다.
Google의 구성요소는 광학 시스루 디스플레이를 위해 구축된 권장사항을 통합하며, Google은 이를 API에 직접 빌드하여 모든 개발자가 아름답고 기능적인 광학 시스루 환경을 구축할 수 있도록 지원합니다.
이와 같은 광학 시스루 기기용 앱을 설계하고 빌드할 때는 몇 가지 사항을 염두에 두어야 합니다.
첫째, 투명 디스플레이이므로 검은색이 없습니다.
세상이 보이면 검은색이 투명해집니다.
따라서 어두운 배경을 사용하여 UI, 텍스트, 콘텐츠의 대비를 유지할 수 없습니다.
검은색은 보이지 않지만 다른 디지털 콘텐츠를 가리는 데 사용할 수 있습니다.
Glimmer UI 프레임워크에는 UI 요소를 서로 쉽게 분리하고 계층 구조를 제공할 수 있는 깊이 API가 있습니다.
여기에서 이 스택의 카드 깊이가 서로 분리된 상태를 유지하는 데 어떻게 도움이 되는지 확인할 수 있습니다.
Google의 시각 과학자들은 대부분의 조명 조건에서 보이는 텍스트와 아이콘의 목표 명암비를 제공했습니다.
야스민이 보여준 것처럼, 이 기능은 프레임워크에 내장되어 있어 표면 색상을 맞춤설정할 때 인터페이스가 사용자에게 잘 보일 것이라고 확신할 수 있습니다.
Google은 이러한 권장사항을 확장 중인 구성요소 라이브러리에 통합했습니다.
최근에 새로운 구성요소가 많이 출시되었습니다.
최신 버전 중 하나는 우아한 모션 전환과 동작이 내장된 페이저 구성요소입니다.
val pagerState =
rememberGlimmerPagerState(pageCount = { 10 })
GlimmerHorizontalPager(pagerState) { page ->
/* Card Content */
}Compose의 가로 페이저에 익숙하다면 이 구성요소도 매우 익숙할 것입니다.
또한 페이저, 스택, 기타 Glimmer UI와 원활하게 통합되도록 설계된 네 가지 새로운 카드 변형을 도입했습니다.
선행 이미지 카드와 후행 이미지 카드는 텍스트와 이미지를 나란히 표시합니다.
따라서 텍스트와 시각적 정보의 양이 동일한 경우 이러한 카드 변형을 사용하세요.
이미지 카드를 별도의 카드 변형으로 분리했습니다.
이 동영상 선택 도구와 같이 한 줄의 텍스트나 설명이 있는 큰 이미지가 있는 경우에 사용하기 가장 좋습니다.
작업 카드 변형은 카드에 단일 버튼을 삽입합니다.
이 도착지 선택 도구와 같이 사용자가 카드를 활성화할 때 어떤 일이 일어날지 정확히 알 수 있도록 하려면 이 옵션을 사용하세요.
버튼 그룹을 사용하면 작은 공간에 많은 버튼을 넣을 수 있습니다.
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") }
}이 간단한 코드만으로도 멋진 전환, 스크롤 동작, 가장자리를 숨기고 필요할 때만 표시되는 스크림, 안경에서 컨트롤로 자동 라우팅되는 입력을 얻을 수 있습니다.
버튼 그룹에는 새로운 아이콘 버튼과 전환 버튼도 포함될 수 있습니다.
var buttonChecked by remember { mutableStateOf(false) }
ToggleButton(
checked = buttonChecked,
leadingIcon = { Icon(FavoriteIcon) },
onCheckedChange = { buttonChecked = it }
) {
Text(if (buttonChecked) "On" else "Off")
}여기에서 이 미디어 컨트롤을 만드는 데 사용되는 모든 버튼 유형을 확인할 수 있습니다.
이제 다양한 배경에서 UI를 쉽게 테스트할 수 있습니다.
안경 에뮬레이터를 사용하면 360도 파노라마 사진을 넣고 화면을 이동하여 다양한 배경, 복잡한 배경, 밝은 배경, 어두운 배경에서 테스트할 수 있습니다.
버튼 그룹은 이와 같은 세부정보 뷰에서 상황별 작업을 표시하는 데 사용하기 좋습니다.
탭하면 공유 요소 전환을 사용하여 상세 뷰어 상태로 이동한 다음 시스템 뒤로 동작을 사용하여 이전 뷰로 돌아갈 수 있습니다.
정말 좋네요.
또한 음성 입력 표시기와 알림 대화상자가 추가되었습니다.
새로운 기능이 많이 추가되었습니다.
새로운 기능을 몇 가지 살펴봤으니 이제 무언가를 만들어 보겠습니다.
Glimmer를 사용하여 지능형 글라스를 빌드하는 것 외에도 Glimmer를 사용하여 유선 XR 글라스 환경을 빌드할 수 있습니다.
또한 실제 위치 기반 앵커를 시작으로 주변 세상을 증강하는 환경을 구축할 수 있습니다.
개발자 컨퍼런스에 참석하기 위해 새로운 도시에 도착했고, 주변에서 먹을거리를 찾고 있다고 가정해 보겠습니다.
자주 사용하는 내비게이션 앱에 주변 음식점과 평점을 찾아 달라고 요청할 수 있습니다.
정보를 위치에 직접 오버레이하여 컨텍스트, 인텔리전스, 안내를 제공할 수 있습니다.
여기에서 아이콘 마커라는 최신 Glimmer 구성요소를 확인할 수 있습니다.
이를 통해 실제 세계의 주요 요소를 강조 표시하고 이 카드와 같은 다른 Glimmer 구성요소와 페어링할 수 있습니다.
이 예시에서는 Google 지도, 장소, Geospatial API를 사용하여 관심 장소에 주석을 추가합니다.
데렉, 설정 방법을 알려주세요.
그럼 시작해 보겠습니다.
좋습니다.
좋습니다.
폴이 앞서 언급했듯이, 이제 빌드를 시작해 보겠습니다.
developer.android.com/xr의 개발자 문서를 사용하고 Jetpack Compose Glimmer 구성요소 라이브러리를 살펴보겠습니다.
따라서 Glimmer에서 사용할 수 있는 모든 구성요소의 시각적 개요를 확인할 수 있습니다.
폴이 앞서 보여준 UI부터 살펴보겠습니다.
따라서 Glimmer UI를 사용하여 공간 표시기와 정보 상자를 만들겠습니다.
먼저 사용할 구성요소를 식별해 보겠습니다.
왼쪽에는 아이콘 마커가 표시되고 오른쪽에는 장소를 설명하는 카드가 표시됩니다.
Android 스튜디오로 이동하면 다른 Compose 구성요소와 마찬가지로 이 창을 미리 볼 수 있으며 변경사항을 실시간으로 확인할 수 있습니다.
앞서 언급했듯이 다양한 유형의 배경 환경에서 UI를 미리 보고 어두운 환경과 밝은 환경과 같은 여러 유형의 시나리오에서 UI의 대비가 충분한지 확인할 수 있습니다.
이제 캔버스가 설정되었으므로 행을 만들고, 간격을 두고 아이콘 마커를 넣고, 샘플 텍스트가 있는 카드를 넣습니다.
이것으로 설명을 마칩니다.
2D 프리뷰가 마음에 들면 실제 세상에 무언가를 표시할 수 있으며, UI를 공간화하여 이를 수행합니다.
즉, 2D가 아닌 3D로 배치합니다.
따라서 공간 UI는 사용자 앞의 3D 공간인 하위 공간에 포함됩니다.
Subspace {
SpatialPanel {
VideoPlayer()
}
}기본적으로 하위 공간은 실제로 사용자로부터 적당한 거리에 콘텐츠를 배치합니다.
Geospatial API를 사용하는 방법을 보여드리기 전에 먼저 근처에 무언가를 배치해 보겠습니다.
앞서 언급한 하위 공간 컴포저블에는 추적 대상을 지정할 수 있는 선택적 매개변수가 있습니다.
val state by
qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
SpatialPanel {
Row {
IndicatorDot()
Spacer()
QrCodeCard(state)
}
}
}따라서 실제 세계에 무언가를 연결하려면 먼저 Jetpack XR용 ARCore에서 가져온 앵커를 만들어야 합니다.
이 예시에서는 QR 코드 인식 기능을 사용하고 있습니다.
이렇게 하면 QR 코드를 읽고 추적할 수 있으므로 콘텐츠가 공간에서 QR 코드를 따라갈 수 있으며, 앞서 살펴본 UI를 연결할 수 있습니다.
좋습니다.
지금까지 주변의 실제 환경에 디지털 콘텐츠를 배치하는 방법을 보여드렸습니다.
폴의 예시로 돌아가 보겠습니다.
저는 네덜란드 출신입니다.
마운틴뷰는 잘 몰라요.
이 이벤트가 끝나면 재미있는 커피숍을 찾는 데 도움이 되는 앱을 만들 수 있도록 도와드리겠습니다.
이제 Jetpack XR용 ARCore에 있는 Geospatial API를 사용하여 사용자가 이러한 장소를 찾도록 돕는 방법을 살펴보겠습니다.
원래 스마트폰에서 출시되었던 Geospatial API를 이제 Android XR에서도 사용할 수 있습니다.
Google 스트리트 뷰의 데이터를 사용하고 이를 GPS 측정값과 결합하여 지원되는 지역에서 기기의 위치를 파악합니다.
따라서 지능형 글라스에서 Geospatial API를 사용하여 사용자의 위치와 방향을 정확하게 파악할 수 있으며, 이를 통해 고정밀 세부 경로 안내 내비게이션 사용 사례를 구현할 수 있습니다.
유선 XR 글라스와 같은 몰입형 기기에서는 Geospatial API를 사용하여 실제 세계에 정확하게 배치된 환경을 구축할 수 있습니다.
예를 들어 건물에 공간 콘텐츠를 고정하거나 전문적인 도시 투어를 만들 수 있습니다.
그럼 다음 세 단계에 따라 Geospatial API를 시작해 보겠습니다.
첫 번째는 Google Cloud Platform 프로젝트를 만들고 앱에서 인증을 구성하는 것입니다.
그런 다음 앱에서 Geospatial API를 구성하고 사용 설정하면 사용자에게 세밀한 위치 정보를 가져올 수 있는 권한을 요청합니다.
그런 다음 Geospatial API를 사용하도록 세션을 구성하면 모든 준비가 완료됩니다.
새롭게 업데이트된 Android XR 개발자 페이지에서 Geospatial API에 관한 개발자 문서의 모든 세부정보를 확인할 수 있습니다.
먼저 사용자의 위치를 사용하여 Google Maps Platform의 Places API에 사용자와 가까운 좋은 카페를 찾아달라고 요청해 보겠습니다.
먼저 사용자가 어디에 있는지 확인합니다.
Geospatial API에 사용자의 위치에 관한 지속적인 업데이트를 요청합니다.
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)
}따라서 XR 세션을 통해 Geospatial 객체에 액세스합니다.
그런 다음 Geospatial이 필요한 센서를 초기화하고 Cloud API에 연결하는 동안 Geospatial이 실행될 때까지 기다립니다.
그런 다음 일정 수준의 정확도가 확보될 때까지 기다립니다.
따라서 GPS 측정값이 수신되거나 시각적 위치 확인 시스템이 사용자의 위치에서 일치하는 항목을 찾으면 정확도 수준이 향상될 수 있습니다.
또한 위치 기반 환경이 올바른 위치에 고정되고 사용자의 주변 지역에서 결과를 가져오려면 높은 정확도 수준을 기다리는 것이 중요합니다.
그런 다음 Geospatial API의 위치 값을 사용하여 Places API로 검색을 수행할 수 있습니다.
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()따라서 Places API와 함께 SearchNearbyRequest를 사용하여 이전에 가져온 사용자 위치 주변의 장소를 쿼리하고 100m 이내의 결과를 찾습니다.
장소의 ID, 표시 이름, 평점, 위치를 가져옵니다.
마지막으로 장소 유형 필터를 포함하여 지정된 매개변수로 검색을 실행합니다.
데이터에 관한 설명은 여기까지입니다. UI로 돌아가겠습니다.
따라서 이전의 마커와 정보 상자를 배치하는 방법을 알아내야 하며, 이를 위해 지리 공간 앵커를 사용합니다.
다시 코드로 돌아가서 XR 세션을 사용하여 Geospatial API에 액세스한 다음, 앞서 검색 결과에서 얻은 특정 위도와 경도를 사용하여 지리 공간 앵커를 만듭니다.
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()
)
}이 예에서는 고도를 해당 좌표의 지형에서 3m 위로 지정했지만 지원되는 옥상에 연결할 수도 있습니다. 즉, 고도 값은 해당 위치에 있는 건물의 옥상을 기준으로 결정됩니다.
따라서 앵커의 회전 값과 같이 지정하는 쿼터니언을 사용하여 앵커를 회전할 수도 있습니다.
하지만 이 예시에서는 UI가 사용자를 향하도록 회전하므로 기본값을 사용합니다.
그런 다음 이전에 만든 Glimmer UI를 이 앵커에 연결하면 공간에 고정된 환경을 구축할 수 있습니다.
when (anchorResult) {
is AnchorCreateSuccess ->
Subspace(FollowTarget.anchor(anchorResult.anchor)) {
SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
PlaceIndicator(place)
}
}
else ->
Log.e("Demo", "Failed to create anchor $anchorResult")
}따라서 Geospatial API를 사용하면 우수한 길 찾기 환경을 제공할 수 있으며, Glimmer를 사용해 디자인하면 특히 시스루 디스플레이에 적합한 한눈에 파악할 수 있는 사용자 환경을 만들 수 있습니다.
Geospatial API에 관한 자세한 내용은 최근 업데이트된 developer.android.com/xr을 참고하세요.
이제 야스민에게 마이크를 넘겨 마무리하겠습니다.
이 세션에서는 Android XR의 몇 가지 기능인 Jetpack XR용 ARCore와 Jetpack Compose Glimmer를 살펴보았습니다.
Android XR이 유선 XR 글라스에 어떤 새로운 기능을 제공할지 기대됩니다.
개발자들은 이미 Android XR용 앱을 빌드하고 있습니다. Google Play 스토어에서 여러분의 창의적인 작품을 만나볼 수 있기를 기대합니다.
시작하려면 개발자 문서를 확인하고, XR 실험에서 영감을 얻고, Codelab을 통해 직접 경험해 보세요.
여러분 모두를 위해 다양한 XR 세션, 데모, 워크숍을 준비했습니다.
몰입형 및 증강 환경 전반에서 Android XR용으로 빌드하는 방법을 알아보세요.
Android XR팀을 대표하여 참석해 주신 모든 분께 감사드리며, Android Developer Summit에서 남은 시간을 즐겁게 보내시기 바랍니다.
감사합니다.
android skills add display-glasses-with-jetpack-compose-glimmerval 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 */
}
}val pagerState =
rememberGlimmerPagerState(pageCount = { 10 })
GlimmerHorizontalPager(pagerState) { page ->
/* Card Content */
}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") }
}var buttonChecked by remember { mutableStateOf(false) }
ToggleButton(
checked = buttonChecked,
leadingIcon = { Icon(FavoriteIcon) },
onCheckedChange = { buttonChecked = it }
) {
Text(if (buttonChecked) "On" else "Off")
}Subspace {
SpatialPanel {
VideoPlayer()
}
}val state by
qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
SpatialPanel {
Row {
IndicatorDot()
Spacer()
QrCodeCard(state)
}
}
}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)
}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()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()
)
}when (anchorResult) {
is AnchorCreateSuccess ->
Subspace(FollowTarget.anchor(anchorResult.anchor)) {
SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
PlaceIndicator(place)
}
}
else ->
Log.e("Demo", "Failed to create anchor $anchorResult")
}