Старший менеджер по продукту
Выйдите за пределы экрана и закрепите приложения в реальном мире с помощью Android XR. На этом занятии вы узнаете, как создавать геопространственные объекты Android XR с помощью Jetpack Compose Glimmer и Geospatial API от ARCore. Сначала мы расскажем, как получить геопространственную позу устройства с помощью ARCore. Затем вы узнаете, как привязать минималистичные компоненты интерфейса Glimmer к реальным местам с помощью системы визуального позиционирования (VPS). Мы объединим их, чтобы создать простой пространственный интерфейс с учетом местоположения.
Расшифровка
Здравствуйте! Добро пожаловать на первое занятие по Android XR в рамках Саммита для разработчиков Android.
Да!
Мы рады, что так много людей интересуется Android XR.
Меня зовут Ясмин Кинтана Эвджен. Со мной Пол Хувер и Дерек Брайди. Они присоединятся к нам по ходу разговора.
Сегодня мы поговорим об Android XR и о том, как создавать пространственно привязанные приложения для реального мира с помощью Jetpack Compose Glimmer и ARCore для Jetpack XR.
Но сначала давайте кратко познакомимся с Android XR.
Android XR – это новая платформа, которая меняет представление о том, как пользователи взаимодействуют с вашим приложением, выходя за рамки экрана в окружающее пространство.
Мы разрабатываем платформу, которая позволяет переносить цифровой опыт в физическую среду.
Это могут быть как наложения, которые улучшают восприятие реального мира, так и бесконечная иммерсивная среда.
Это та же открытая экосистема Android, которую мы вместе создавали годами, но теперь холст для творчества стал размером с комнату и ограничен только вашим воображением.
Мы рассматриваем XR как спектр устройств, от умных очков, которые обеспечивают легкое дополнение реальности с помощью аудио и видео, до иммерсивных шлемов и проводных XR-очков, которые помогают сосредоточиться, повысить продуктивность и погрузиться в развлечения.
Новейшее устройство в семействе Android XR – XREAL AURA. Его уже можно купить.
Это легкое и мощное устройство, которое позволяет использовать XR-технологии в привычной среде Android.
Мы стремимся к тому, чтобы разработчики, которые уже создают приложения для Android, могли без труда перейти на XR.
Мы хотим, чтобы вы могли создавать приложения для Android XR, используя привычные модели, библиотеки и инструменты.
Существующее 2D-приложение, совместимое с Android, будет работать на шлемах Android XR и проводных XR-очках без изменений или с минимальными доработками.
Чтобы вы могли создавать потрясающие XR-приложения, которые будут ощущаться как дома, мы предоставляем набор библиотек, специально созданных для платформы XR.
Если вы привыкли создавать продукты на других платформах с эффектом присутствия, то можете использовать OpenXR и наши 3D-движки, такие как Unity, Godot и Unreal.
Давайте поговорим о создании приложений с помощью библиотек Android.
Мы предлагаем Jetpack XR SDK – набор библиотек Kotlin, которые помогают расширить возможности приложений для XR в иммерсивных режимах и режимах дополненной реальности.
Давайте посмотрим на наши библиотеки иммерсивных представлений.
В этом году мы выпустили бета-версии и стабильные версии библиотек Jetpack XR, которые помогут вам создавать приложения с эффектом присутствия.
Уровень восприятия – это ARCore для Jetpack XR.
Эта библиотека предоставляет информацию о реальном мире, например о том, где находятся плоские поверхности, такие как стол, и как расположены руки пользователей.
Оно может отслеживать точки в трехмерном пространстве и передавать эту информацию приложениям, если у них есть необходимые разрешения.
Jetpack XR SceneCore предоставляет граф сцены для размещения пространственного контента.
Эта библиотека позволяет представлять объекты в трехмерном пространстве и обеспечивает пространственное поведение для точного размещения графа сцены.
А Jetpack Compose для XR предоставляет новые composable-функции подпространства, созданные специально для 3D.
Разработчики Android, которые уже знакомы с макетами Compose, например со строками и столбцами, смогут легко создавать пространственные интерфейсы.
Создав пространственную строку, вы можете декларативно разместить несколько пространственных панелей рядом друг с другом, не вычисляя вручную их положение или размер.
Используя composable-функции подпространства, например пространственную панель или орбитер, вы можете легко расширить свои 2D-компоненты в это новое 3D-пространство.
И, конечно, composable-функции Subspace способны не только показывать 2D-контент.
Compose for XR поддерживает пространственные видео и 3D-модели через пространственную внешнюю поверхность и пространственную модель glTF.
Теперь поговорим о приложениях с дополненной реальностью.
Библиотека Jetpack Projected позволяет улучшить существующее мобильное приложение, чтобы использовать аппаратное обеспечение умных очков, например датчики и дисплей.
Наконец, Jetpack Compose Glimmer – это набор инструментов пользовательского интерфейса для создания приложений Android XR, оптимизированных для очков.
Jetpack Compose Glimmer состоит из стилей и готовых к использованию компонентов для создания XR-приложений.
Мы запустили Glimmer в прошлом году и продолжаем расширять его возможности, в том числе добавили агентский навык, который поможет вам создавать приложения для видеоочков с помощью Glimmer.
android skills add display-glasses-with-jetpack-compose-glimmerС момента запуска мы прислушивались к вашим отзывам.
Мы получили много просьб расширить поддержку Jetpack Compose Glimmer за пределы умных очков.
Мы рады сообщить, что внесли изменения, которые улучшат работу с пространственно привязанными объектами в Glimmer. Подробнее об этом мы поговорим позже.
Оптимизация Glimmer началась с создания новой поверхности для отображения на различных устройствах XR, от дисплея до проводных XR-очков.
Добавление этой новой поверхности обеспечивает большую гибкость при настройке пользовательского интерфейса.
А чтобы сделать бренд вашего приложения более узнаваемым, мы добавили пользовательские цвета для компонентов 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 Studio можно использовать эмулятор, чтобы посмотреть, как интерфейс очков будет выглядеть на разных фонах, например светлом, темном или насыщенном деталями.
Это лишь малая часть того, что мы планируем сделать.
Нет.
Моя команда говорила, что это не смешно, но я все равно решил попробовать.
А теперь я передаю слово Полу, который расскажет вам больше о Jetpack Compose Glimmer.
Тестирование. Все в порядке.
Мне нужно нажать кнопку "Далее".
Спасибо, Ясмин.
Теперь
Как сказала Ясмин, Jetpack Compose Glimmer предоставляет базовые строительные блоки для разработки приложений, адаптированных для видеоочков.
Наши компоненты включают в себя лучшие практики, созданные для оптических прозрачных дисплеев, и мы встроили их непосредственно в API для вас, ребята, что позволяет всем разработчикам создавать красивые и функциональные оптические прозрачные дисплеи.
При разработке приложений для устройств с прозрачными оптическими дисплеями, таких как это, необходимо учитывать несколько факторов.
Во-первых, это прозрачный дисплей, то есть на нем нет черного цвета.
Когда мир виден, черный цвет прозрачен.
Поэтому вы не можете рассчитывать на темный фон для поддержания контраста в интерфейсе, тексте и контенте.
Хотя черный цвет невидим, вы можете использовать его, чтобы скрыть другой цифровой контент.
В фреймворке Glimmer UI есть API глубины, который позволяет легко разделять элементы интерфейса и создавать ощущение иерархии.
Здесь вы можете увидеть, как глубина карточек в этой стопке помогает им оставаться отделенными друг от друга.
Наши специалисты по зрению определили целевые коэффициенты контрастности для текста и значков, которые видны в большинстве условий освещения.
Как показала Ясмин, мы можем встроить это или мы встроили это в фреймворк, чтобы при настройке цветов поверхности вы могли быть уверены, что ваш интерфейс будет разборчивым для ваших пользователей.
Эти и другие рекомендации мы включили в нашу расширяющуюся библиотеку компонентов.
За последнее время мы добавили много новых компонентов.
Одно из наших последних дополнений – компонент пейджера с плавными переходами и встроенными жестами.
val pagerState =
rememberGlimmerPagerState(pageCount = { 10 })
GlimmerHorizontalPager(pagerState) { page ->
/* Card Content */
}Если вы знакомы с горизонтальным пейджером Compose, этот компонент будет вам действительно знаком.
Мы также добавили четыре новых варианта карточек, которые легко интегрируются с пейджерами, стеками и другими элементами интерфейса Glimmer.
На карточках с изображением в начале или в конце текст и изображение расположены рядом.
Используйте эти варианты карточек, когда у вас есть равное количество текста и визуальной информации.
Мы выделили карточку с изображением в отдельный вариант.
Этот вариант лучше всего подходит, если у вас есть большое изображение с одной строкой текста или подписью, как в этом окне выбора видео.
Вариант карточки с действием содержит одну кнопку.
Используйте этот вариант, когда хотите, чтобы пользователи точно знали, что произойдет при активации карточки, как в этом средстве выбора пункта назначения.
Группы кнопок позволяют разместить много кнопок на небольшом пространстве.
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")
}Здесь вы можете увидеть все типы кнопок, которые используются для создания этого элемента управления мультимедиа.
Теперь вы можете легко протестировать интерфейс на разных фонах.
Эмулятор очков позволяет загружать панорамные фотографии и поворачивать их, чтобы тестировать субтитры на разных фонах, например на светлом, темном или с большим количеством деталей.
Группы кнопок отлично подходят для использования в подробном представлении, например, для отображения контекстных действий.
При нажатии вы можете использовать переходы между общими элементами, чтобы перейти в режим подробного просмотра, а затем использовать системный жест "Назад", чтобы вернуться к предыдущему представлению.
Очень мило.
Кроме того, мы добавили индикатор голосового ввода и диалоговые окна с предупреждениями.
В общем, много нового.
Теперь, когда мы познакомились с новыми возможностями, давайте что-нибудь создадим.
Glimmer можно использовать не только для создания умных очков, но и для разработки функций проводных XR-очков.
Мы хотим, чтобы вы создавали продукты, которые дополняют мир вокруг вас, начиная с привязки к реальным местам.
Представьте, что вы приехали в другой город, например на конференцию разработчиков, и хотите найти, где поесть.
Например, вы можете попросить навигационное приложение найти рестораны поблизости и показать их оценки.
Информация может накладываться непосредственно на местоположение, предоставляя контекст, аналитику и рекомендации.
Здесь вы видите наш последний компонент Glimmer, который называется Icon Marker.
Она позволяет выделять ключевые элементы в физическом мире и связывать их с другими компонентами Glimmer, такими как эта карточка.
В этом примере он добавляет аннотации к объектам инфраструктуры, используя Google Maps API, Places API и Geospatial API.
Дерек, давай покажем, как это настроить.
Давай попробуем.
Теперь
Теперь
Как уже говорил Пол, давайте начнем.
Мы воспользуемся документацией для разработчиков на сайте developer.android.com/xr и рассмотрим библиотеку компонентов Jetpack Compose Glimmer.
Так вы сможете найти визуальный обзор всех компонентов, доступных в Glimmer.
И давайте начнем с этой части пользовательского интерфейса, которую Пол показал ранее.
Поэтому мы будем использовать Glimmer UI для создания пространственного индикатора и информационного окна.
Для начала определим, какие компоненты мы будем использовать.
Слева вы увидите маркер со значком, а справа – карточку с описанием места.
Если вы перейдете в Android Studio, то сможете увидеть эту панель, как и любой другой компонент Compose, и посмотреть изменения в реальном времени.
Как уже упоминалось, вы можете предварительно просмотреть пользовательский интерфейс на всех типах фонов и убедиться, что контрастность пользовательского интерфейса достаточна в различных сценариях, например в темных и светлых средах.
Теперь, когда холст настроен, мы создадим строку, поместим в нее маркер значка с некоторым интервалом и карточку с образцом текста.
Вот и всё.
Если вас устраивает предварительный просмотр в 2D, мы можем начать отмечать что-то в реальном мире, и мы сделаем это, пространственно разместив пользовательский интерфейс.
Это значит, что мы будем размещать их в 3D, а не в 2D.
Пространственные интерфейсы содержатся в подпространствах, которые представляют собой трёхмерное пространство перед пользователем.
Subspace {
SpatialPanel {
VideoPlayer()
}
}По умолчанию подпространство размещает свое содержимое на комфортном расстоянии от пользователя.
Прежде чем мы покажем вам, как использовать Geospatial API, давайте разместим что-нибудь поблизости.
Итак, у composable-функции Subspace, о которой я говорил ранее, есть необязательный параметр, позволяющий указать цель для следования.
val state by
qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
SpatialPanel {
Row {
IndicatorDot()
Spacer()
QrCodeCard(state)
}
}
}Чтобы прикрепить объект к реальному миру, сначала создайте якорь, полученный из ARCore для Jetpack XR.
В этом примере я использую функцию распознавания QR-кода.
Это позволит вам считывать и отслеживать QR-коды, чтобы ваш контент мог следовать за этим QR-кодом в пространстве, и мы прикрепим пользовательский интерфейс, который мы видели ранее.
Окей.
Итак, я показал вам, как размещать цифровой контент в реальном мире поблизости.
Вернемся к примеру с Полом.
Я из Нидерландов.
Я не очень хорошо знаю Маунтин-Вью.
Поэтому после этого мероприятия я хочу помочь вам создать приложение, которое поможет вам найти интересные кофейни.
Давайте посмотрим, как помочь пользователям найти их с помощью Geospatial API, который теперь находится в ARCore для Jetpack XR.
Поэтому Geospatial API, который изначально был запущен на смартфонах, теперь доступен и на Android XR.
Она использует данные Google Просмотра улиц и GPS, чтобы определить местоположение устройства в поддерживаемых регионах.
Например, на умных очках Geospatial API можно использовать для точного определения местоположения и направления движения пользователя, что позволяет реализовать высокоточную пошаговую навигацию.
А на устройствах с эффектом присутствия, таких как проводные XR-очки, Geospatial API позволяет создавать объекты, точно позиционированные в реальном мире.
Например, вы можете привязать пространственный контент к зданиям или создать специализированные экскурсии по городу.
Итак, давайте начнем работу с Geospatial API, выполнив эти три шага.
Сначала вы создадите проект Google Cloud Platform и настроите аутентификацию в приложении.
Затем, когда Geospatial API будет настроен и включен в вашем приложении, вы запросите у пользователя разрешение на получение его местоположения с высокой точностью.
Затем настройте сеанс для использования Geospatial API.
Всю информацию о Geospatial API вы найдете в документации для разработчиков на обновленной странице Android XR.
Итак, давайте начнем с использования местоположения пользователя, чтобы запросить у Places API из платформы Google Карт информацию о хороших кафе поблизости.
Во-первых, где находится пользователь?
Мы попросим 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 запустится, инициализирует необходимые датчики и подключится к Cloud API.
Затем мы подождем, пока не будет достигнут определенный уровень точности.
Таким образом, точность может повышаться по мере поступления данных GPS или когда система визуального позиционирования находит совпадения в местоположении пользователя.
Это важно, чтобы результаты поиска были привязаны к правильному месту и пользователь видел то, что находится рядом с ним.
Теперь мы готовы выполнить поиск с помощью Places API, используя значения местоположения из Geospatial 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()Поэтому мы будем использовать SearchNearbyRequest с Places API, чтобы запрашивать места вокруг позиции пользователя, которую мы получили ранее, и мы найдем результаты в пределах 100 м.
Мы получим идентификатор места, его отображаемое название, рейтинг и, конечно, местоположение.
Наконец, мы выполним поиск с заданными параметрами, включая фильтр по типу места.
Итак, мы закончили с данными. Давайте вернемся к интерфейсу.
Итак, нам нужно понять, как разместить маркер и информационное окно, которые мы создали ранее. Для этого мы будем использовать геопространственные привязки.
Вернемся к коду. Мы снова получим доступ к Geospatial API, используя сеанс XR, а затем создадим пространственный якорь, используя определенные широту и долготу, полученные ранее из результатов поиска.
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()
)
}В этом примере мы указали высоту 3 м над поверхностью земли в этих координатах, но вы также можете прикрепить объект к поддерживаемым крышам, и тогда значение высоты будет определяться относительно крыши здания в этом месте.
Вы также можете повернуть привязку с помощью кватерниона, который задает значения поворота привязки.
Но в этом примере мы будем использовать значения по умолчанию, потому что мы заставим пользовательский интерфейс поворачиваться лицом к пользователю.
Затем мы прикрепим к этому якорю интерфейс Glimmer, который создали ранее. Это и есть основа для пространственного интерфейса.
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: ARCore для Jetpack XR и Jetpack Compose Glimmer.
Мы с нетерпением ждем, когда новые функции Android XR станут доступны на проводных XR-очках.
Разработчики уже создают приложения для Android XR, и мы надеемся увидеть ваши творческие работы в Google Play.
Чтобы начать, ознакомьтесь с документацией для разработчиков, вдохновитесь нашими экспериментами с расширенной реальностью и попробуйте практические работы.
Мы подготовили для вас множество интересных сессий, демонстраций и семинаров по расширенной реальности.
Присоединяйтесь к нам и узнайте, как создавать иммерсивные приложения и приложения дополненной реальности для Android XR.
От имени команды Android XR благодарим вас за участие. Надеемся, что вам понравится оставшаяся часть Саммита для разработчиков Android.
Спасибо.
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")
}