Android XR

Как создавать контент с привязкой к миру для проводных XR-очков

Колонки

О приложении

О выступлении

Выйдите за пределы экрана и закрепите приложения в реальном мире с помощью Android XR. На этом занятии вы узнаете, как создавать геопространственные объекты Android XR с помощью Jetpack Compose Glimmer и Geospatial API от ARCore. Сначала мы расскажем, как получить геопространственную позу устройства с помощью ARCore. Затем вы узнаете, как привязать минималистичные компоненты интерфейса Glimmer к реальным местам с помощью системы визуального позиционирования (VPS). Мы объединим их, чтобы создать простой пространственный интерфейс с учетом местоположения.

Сводка

  • Android XR позволяет взаимодействовать с цифровым контентом в физическом мире с помощью различных устройств, от умных очков до проводных XR-очков. Существующие 2D-приложения для Android работают на XR-шлемах и проводных XR-очках с минимальными изменениями.
  • Jetpack Compose Glimmer предоставляет компоненты и стили, адаптированные для оптических прозрачных дисплеев. Встроенные вспомогательные функции корректируют цвета бренда, чтобы они были безопасны для очков и сохраняли видимость и контрастность при любом освещении.
  • Библиотека Glimmer добавляет новые компоненты, такие как пейджеры и группы кнопок. Depth API помогает разделять наложенные друг на друга элементы на прозрачных дисплеях, где черный цвет является прозрачным.
  • Composable-функции Subspace размещают интерфейсы в 3D-пространстве и могут следовать за якорями ARCore через цель отслеживания, например отслеживаемый QR-код.
  • Geospatial API в ARCore объединяет данные Просмотра улиц и GPS, чтобы точно определять местоположение устройств в поддерживаемых регионах. Вы можете прикреплять компоненты Glimmer к реальным геопространственным привязкам, используя координаты из запросов Places API.

Ресурсы

Gemini Notebook

Темы

  • android-xr
  • jetpack-compose
  • мерцание
  • arcore
  • geospatial-api
  • augmented-reality

Расшифровка

Знакомство с Android XR

  • Здравствуйте! Добро пожаловать на первое занятие по 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-очках без изменений или с минимальными доработками.

Jetpack XR SDK и инструменты разработчика

  • Чтобы вы могли создавать потрясающие 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 Compose Glimmer

  • Теперь поговорим о приложениях с дополненной реальностью.

  • Библиотека 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 и платформы Google Карт

  • Окей.

  • Итак, я показал вам, как размещать цифровой контент в реальном мире поблизости.

  • Вернемся к примеру с Полом.

  • Я из Нидерландов.

  • Я не очень хорошо знаю Маунтин-Вью.

  • Поэтому после этого мероприятия я хочу помочь вам создать приложение, которое поможет вам найти интересные кофейни.

  • Давайте посмотрим, как помочь пользователям найти их с помощью 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.

  • Спасибо.

Код

Фрагменты кода

Фрагмент Shell на 04:39 в видео "Создание интерфейса с помощью Jetpack Compose Glimmer"
android skills add display-glasses-with-jetpack-compose-glimmer
Фрагмент кода на Kotlin в видео "Создание интерфейса с помощью 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 */
   }
}
Фрагмент кода Kotlin на 08:37 в видео "Принципы разработки интерфейса для видеоочков"
val pagerState =
  rememberGlimmerPagerState(pageCount = { 10 })

GlimmerHorizontalPager(pagerState) { page ->
   /* Card Content */
}
Фрагмент кода на Kotlin в видео "Принципы разработки интерфейса для видеоочков"
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") }
}
Фрагмент кода Kotlin на 10:13 в видео "Принципы дизайна интерфейса для видеоочков"
var buttonChecked by remember { mutableStateOf(false) }

ToggleButton(
  checked = buttonChecked,
  leadingIcon = { Icon(FavoriteIcon) },
  onCheckedChange = { buttonChecked = it }
) {
  Text(if (buttonChecked) "On" else "Off")
}
Фрагмент кода на Kotlin в видео "Обзор якорей на основе местоположения"
Subspace {
  SpatialPanel {
    VideoPlayer()
  }
}
Фрагмент кода Kotlin на 14:16 в видео "Обзор привязок на основе местоположения"
val state by
  qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
  SpatialPanel {
    Row {
      IndicatorDot()
      Spacer()
      QrCodeCard(state)
    }
  }
}
Фрагмент кода Kotlin на 16:43 в видео "Интеграция Geospatial API и платформы Google Карт"
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)

}
Фрагмент кода Kotlin на 17:27 в видео "Интеграция Geospatial API и платформы Google Карт"
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()
Фрагмент кода Kotlin на 18:09 в видео "Интеграция Geospatial API и платформы Google Карт"
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()
 )
}
Фрагмент кода Kotlin на 18:53 в видео "Интеграция Geospatial API и платформы Google Карт"
when (anchorResult) {
 is AnchorCreateSuccess ->
   Subspace(FollowTarget.anchor(anchorResult.anchor)) {
     SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
       PlaceIndicator(place)
     }
   }

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