Android XR SDK теперь доступен в предварительной версии для разработчиков. Нам нужны ваши отзывы! Посетите нашу
страницу поддержки , чтобы связаться с нами.
Разработка пользовательского интерфейса с помощью Jetpack Compose для XR
Оптимизируйте свои подборки
Сохраняйте и классифицируйте контент в соответствии со своими настройками.
С Jetpack Compose для XR вы можете декларативно создавать пространственный пользовательский интерфейс и макет, используя знакомые концепции Compose, такие как строки и столбцы. Это позволяет расширить существующий пользовательский интерфейс Android в трёхмерное пространство или создавать совершенно новые иммерсивные 3D-приложения.
Если вы занимаетесь пространственной визуализацией существующего приложения Android на основе Views, у вас есть несколько вариантов разработки. Вы можете использовать API взаимодействия, использовать Compose и Views вместе или работать напрямую с библиотекой SceneCore. Подробнее см. в нашем руководстве по работе с Views .

Codelab
Изучите основы Android XR: Часть 1 — Режимы и пространственные панели
arrow_forward О подпространствах и пространственных компонентах
При написании приложения для Android XR важно понимать концепции подпространства и пространственных компонентов .
О подпространстве
При разработке для Android XR вам потребуется добавить Subspace
в приложение или макет. Подпространство — это раздел трёхмерного пространства в вашем приложении, где вы можете размещать 3D-контент, создавать 3D-макеты и добавлять глубину двумерному контенту. Подпространство отображается только при включённом пространственном отображении. В Home Space или на устройствах без поддержки XR любой код в этом подпространстве игнорируется.
Существует два способа создания подпространства:
-
Subspace
: этот компонуемый объект можно разместить в любом месте иерархии пользовательского интерфейса вашего приложения, что позволяет поддерживать макеты для двухмерного и пространственного пользовательского интерфейса без потери контекста между файлами. Это упрощает обмен такими элементами, как существующая архитектура приложения, между XR и другими форм-факторами без необходимости переносить состояние по всему дереву пользовательского интерфейса или перестраивать архитектуру приложения. -
ApplicationSubspace
: эта функция создаёт только подпространство уровня приложения и должна располагаться на самом верхнем уровне пространственной иерархии пользовательского интерфейса вашего приложения. ApplicationSubspace
визуализирует пространственный контент с необязательными VolumeConstraints
. В отличие от Subspace
, ApplicationSubspace
не может быть вложен в другое Subspace
или ApplicationSubspace
.
Для получения дополнительной информации см. раздел Добавление подпространства в ваше приложение .
О пространственных компонентах
Компонуемые элементы подпространства : эти компоненты могут быть отрисованы только в подпространстве. Перед размещением в 2D-макете они должны быть заключены в Subspace
или setSubspaceContent()
. SubspaceModifier
позволяет добавлять к компонуемым элементам подпространства атрибуты, такие как глубина, смещение и позиционирование .
Другие пространственные компоненты не требуют вызова внутри подпространства. Они состоят из обычных 2D-элементов, обёрнутых в пространственный контейнер. Эти элементы могут использоваться в 2D- или 3D-макетах, если они определены для обоих. Если пространственное отображение отключено, их пространственные характеристики будут игнорироваться, и они будут возвращены к своим 2D-аналогам.
Создать пространственную панель
SpatialPanel
— это компонуемое подпространство, позволяющее отображать содержимое приложения, например, можно отображать воспроизведение видео, неподвижные изображения или любой другой контент на пространственной панели.

Вы можете использовать SubspaceModifier
для изменения размера, поведения и положения пространственной панели, как показано в следующем примере.
Subspace {
SpatialPanel(
SubspaceModifier
.height(824.dp)
.width(1400.dp)
.movable()
.resizable()
) {
SpatialPanelContent()
}
}
@Composable
fun SpatialPanelContent() {
Box(
Modifier
.background(color = Color.Black)
.height(500.dp)
.width(500.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "Spatial Panel",
color = Color.White,
fontSize = 25.sp
)
}
}
Ключевые моменты кода
- Поскольку API
SpatialPanel
являются компонуемыми подпространствами, их необходимо вызывать внутри Subspace
. Вызов вне подпространства приведёт к исключению. - Размер
SpatialPanel
задаётся с помощью спецификаций height
и width
SubspaceModifier
. Если эти спецификации не заданы, размер панели определяется размерами её содержимого. - Разрешите пользователю изменять размер или перемещать панель, добавляя
movable
или resizable
модификаторы. - Подробную информацию о размерах и расположении см. в нашем руководстве по проектированию пространственных панелей . Более подробную информацию о реализации кода см. в нашей справочной документации .
Как работает модификатор подвижного подпространства
Когда пользователь перемещает панель от себя, модификатор перемещаемого подпространства по умолчанию масштабирует панель аналогично тому, как система изменяет размер панелей в домашнем пространстве . Всё дочернее содержимое наследует это поведение. Чтобы отключить это, установите для параметра scaleWithDistance
значение false
.
Создать орбитальный аппарат
Орбитер — это пространственный компонент пользовательского интерфейса. Он предназначен для присоединения к соответствующей пространственной панели, макету или другому объекту. Орбитер обычно содержит элементы навигации и контекстные действия, связанные с объектом, к которому он привязан. Например, если вы создали пространственную панель для отображения видеоконтента, вы можете добавить элементы управления воспроизведением видео внутрь орбитера.

Как показано в следующем примере, вызовите орбитер внутри 2D-макета в SpatialPanel
, чтобы обернуть пользовательские элементы управления, такие как навигация. Это извлечет их из 2D-макета и прикрепит к пространственной панели в соответствии с вашей конфигурацией.
Subspace {
SpatialPanel(
SubspaceModifier
.height(824.dp)
.width(1400.dp)
.movable()
.resizable()
) {
SpatialPanelContent()
OrbiterExample()
}
}
@Composable
fun OrbiterExample() {
Orbiter(
position = ContentEdge.Bottom,
offset = 96.dp,
alignment = Alignment.CenterHorizontally
) {
Surface(Modifier.clip(CircleShape)) {
Row(
Modifier
.background(color = Color.Black)
.height(100.dp)
.width(600.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Orbiter",
color = Color.White,
fontSize = 50.sp
)
}
}
}
}
Ключевые моменты кода
- Поскольку орбитеры являются пространственными компонентами пользовательского интерфейса, код можно повторно использовать в 2D- и 3D-макетах. В 2D-макете приложение отображает только содержимое внутри орбитера, игнорируя сам орбитер.
- Дополнительную информацию об использовании и проектировании орбитальных аппаратов можно найти в нашем руководстве по проектированию .
Добавить несколько пространственных панелей в пространственную компоновку
Вы можете создать несколько пространственных панелей и разместить их в пространственном макете, используя SpatialRow
, SpatialColumn
, SpatialBox
и SpatialLayoutSpacer
.

В следующем примере кода показано, как это сделать.
Subspace {
SpatialRow {
SpatialColumn {
SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
SpatialPanelContent("Top Left")
}
SpatialPanel(SubspaceModifier.height(200.dp).width(400.dp)) {
SpatialPanelContent("Middle Left")
}
SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
SpatialPanelContent("Bottom Left")
}
}
SpatialColumn {
SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
SpatialPanelContent("Top Right")
}
SpatialPanel(SubspaceModifier.height(200.dp).width(400.dp)) {
SpatialPanelContent("Middle Right")
}
SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
SpatialPanelContent("Bottom Right")
}
}
}
}
@Composable
fun SpatialPanelContent(text: String) {
Column(
Modifier
.background(color = Color.Black)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Panel",
color = Color.White,
fontSize = 15.sp
)
Text(
text = text,
color = Color.White,
fontSize = 25.sp,
fontWeight = FontWeight.Bold
)
}
}
Ключевые моменты кода
Используйте объем для размещения 3D-объекта в макете.
Чтобы разместить 3D-объект в макете, вам потребуется использовать составное подпространство, называемое объёмом. Вот пример того, как это сделать.

Subspace {
SpatialPanel(
SubspaceModifier.height(1500.dp).width(1500.dp)
.resizable().movable()
) {
ObjectInAVolume(true)
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "Welcome",
fontSize = 50.sp,
)
}
}
}
@OptIn(ExperimentalSubspaceVolumeApi::class)
@Composable
fun ObjectInAVolume(show3DObject: Boolean) {
Дополнительная информация
Добавить поверхность для изображений или видеоконтента
SpatialExternalSurface
— это компонуемое подпространство, которое создаёт и управляет Surface
, на которой ваше приложение может отображать контент, например изображение или видео . SpatialExternalSurface
поддерживает как стереоскопический, так и моноскопический контент.
В этом примере показано, как загрузить стереоскопическое видео бок о бок с помощью Media3 Exoplayer и SpatialExternalSurface
:
@OptIn(ExperimentalComposeApi::class)
@Composable
fun SpatialExternalSurfaceContent() {
val context = LocalContext.current
Subspace {
SpatialExternalSurface(
modifier = SubspaceModifier
.width(1200.dp) // Default width is 400.dp if no width modifier is specified
.height(676.dp), // Default height is 400.dp if no height modifier is specified
// Use StereoMode.Mono, StereoMode.SideBySide, or StereoMode.TopBottom, depending
// upon which type of content you are rendering: monoscopic content, side-by-side stereo
// content, or top-bottom stereo content
stereoMode = StereoMode.SideBySide,
) {
val exoPlayer = remember { ExoPlayer.Builder(context).build() }
val videoUri = Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
// Represents a side-by-side stereo video, where each frame contains a pair of
// video frames arranged side-by-side. The frame on the left represents the left
// eye view, and the frame on the right represents the right eye view.
.path("sbs_video.mp4")
.build()
val mediaItem = MediaItem.fromUri(videoUri)
// onSurfaceCreated is invoked only one time, when the Surface is created
onSurfaceCreated { surface ->
exoPlayer.setVideoSurface(surface)
exoPlayer.setMediaItem(mediaItem)
exoPlayer.prepare()
exoPlayer.play()
}
// onSurfaceDestroyed is invoked when the SpatialExternalSurface composable and its
// associated Surface are destroyed
onSurfaceDestroyed { exoPlayer.release() }
}
}
}
Ключевые моменты кода
- Установите
StereoMode
на Mono
, SideBySide
или TopBottom
в зависимости от типа отображаемого контента:-
Mono
: изображение или видеокадр состоит из одного идентичного изображения, показываемого обоим глазам. -
SideBySide
: изображение или видеокадр содержит пару изображений или видеокадров, расположенных бок о бок, где изображение или кадр слева представляет вид левого глаза, а изображение или кадр справа представляет вид правого глаза. -
TopBottom
: Изображение или видеокадр содержит пару изображений или видеокадров, расположенных вертикально, где изображение или кадр сверху представляет вид левого глаза, а изображение или кадр снизу представляет вид правого глаза.
-
SpatialExternalSurface
поддерживает только прямоугольные поверхности. - Эта
Surface
не фиксирует события ввода. - Невозможно синхронизировать изменения
StereoMode
с рендерингом приложения или декодированием видео. - Этот компонуемый элемент не может отображаться поверх других панелей, поэтому не следует использовать перемещаемые модификаторы, если в макете есть другие панели.
Добавить поверхность для видеоконтента, защищенного DRM
SpatialExternalSurface
также поддерживает воспроизведение видеопотоков с защитой DRM. Для этого необходимо создать защищенную поверхность, которая выполняет рендеринг в защищенные графические буферы. Это предотвращает запись экрана или доступ к контенту со стороны незащищенных компонентов системы.
Чтобы создать защищенную поверхность, установите для параметра surfaceProtection
значение SurfaceProtection.Protected
в компонуемом объекте SpatialExternalSurface
. Кроме того, необходимо настроить Media3 Exoplayer с использованием соответствующей информации DRM для обработки получения лицензии с сервера лицензий.
В следующем примере показано, как настроить SpatialExternalSurface
и ExoPlayer
для воспроизведения видеопотока, защищенного DRM:
@OptIn(ExperimentalComposeApi::class)
@Composable
fun DrmSpatialVideoPlayer() {
val context = LocalContext.current
Subspace {
SpatialExternalSurface(
modifier = SubspaceModifier
.width(1200.dp)
.height(676.dp),
stereoMode = StereoMode.SideBySide,
surfaceProtection = SurfaceProtection.Protected
) {
val exoPlayer = remember { ExoPlayer.Builder(context).build() }
// Define the URI for your DRM-protected content and license server.
val videoUri = "https://your-content-provider.com/video.mpd"
val drmLicenseUrl = "https://your-license-server.com/license"
// Build a MediaItem with the necessary DRM configuration.
val mediaItem = MediaItem.Builder()
.setUri(videoUri)
.setDrmConfiguration(
MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri(drmLicenseUrl)
.build()
)
.build()
onSurfaceCreated { surface ->
// The created surface is secure and can be used by the player.
exoPlayer.setVideoSurface(surface)
exoPlayer.setMediaItem(mediaItem)
exoPlayer.prepare()
exoPlayer.play()
}
onSurfaceDestroyed { exoPlayer.release() }
}
}
}
Ключевые моменты кода
- Защищенная поверхность: настройка
surfaceProtection = SurfaceProtection.Protected
в SpatialExternalSurface
необходима для того, чтобы базовая Surface
поддерживалась безопасными буферами, подходящими для содержимого DRM. - Конфигурация DRM: необходимо настроить
MediaItem
, используя схему DRM (например, C.WIDEVINE_UUID
) и URI вашего сервера лицензий. ExoPlayer использует эту информацию для управления сеансом DRM. - Защищённый контент: при рендеринге на защищённой поверхности видеоконтент декодируется и отображается по защищённому пути, что способствует выполнению требований лицензирования контента. Это также предотвращает его появление на снимках экрана.
Добавить другие пространственные компоненты пользовательского интерфейса
Пространственные компоненты пользовательского интерфейса можно размещать в любом месте иерархии пользовательского интерфейса вашего приложения. Эти элементы можно повторно использовать в вашем двухмерном пользовательском интерфейсе, а их пространственные атрибуты будут видны только при включенных пространственных возможностях. Это позволяет добавлять возвышенности к меню, диалоговым окнам и другим компонентам без необходимости писать код дважды. Ознакомьтесь со следующими примерами пространственного пользовательского интерфейса, чтобы лучше понять, как использовать эти элементы.
Компонент пользовательского интерфейса | Когда включено пространственное моделирование | В 2D-среде |
---|
SpatialDialog | Панель слегка отодвинется назад по оси Z, чтобы отобразить приподнятое диалоговое окно. | Возвращается к 2D- Dialog . |
SpatialPopup | Панель слегка отодвинется назад по оси Z, чтобы отобразить всплывающее окно. | Возвращается к 2D Popup . |
SpatialElevation | SpatialElevationLevel можно настроить для добавления высоты. | Показывает без пространственного возвышения. |
SpatialDialog
Это пример диалогового окна, которое открывается с небольшой задержкой. При использовании SpatialDialog
диалоговое окно отображается на той же глубине по оси Z, что и пространственная панель, а при включенной пространственной проекции панель отодвигается назад на 125 дп. SpatialDialog
можно использовать и без пространственной проекции; в этом случае SpatialDialog
возвращается к своему двумерному аналогу Dialog
.
@Composable
fun DelayedDialog() {
var showDialog by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(3000)
showDialog = true
}
if (showDialog) {
SpatialDialog(
onDismissRequest = { showDialog = false },
SpatialDialogProperties(
dismissOnBackPress = true
)
) {
Box(
Modifier
.height(150.dp)
.width(150.dp)
) {
Button(onClick = { showDialog = false }) {
Text("OK")
}
}
}
}
}
Ключевые моменты кода
Создавайте пользовательские панели и макеты
Чтобы создать пользовательские панели, которые не поддерживаются Compose for XR, вы можете работать напрямую с экземплярами PanelEntity
и графом сцены, используя API SceneCore
.
Привязка орбитальных аппаратов к пространственным схемам и другим объектам
Вы можете привязать орбитер к любой сущности, объявленной в Compose. Для этого необходимо объявить орбитер в пространственной структуре элементов пользовательского интерфейса, таких как SpatialRow
, SpatialColumn
или SpatialBox
. Орбитер привязывается к родительской сущности, ближайшей к месту его объявления.
Поведение орбитера определяется тем, где вы его объявляете:
- В 2D-макете, упакованном в
SpatialPanel
(как показано в предыдущем фрагменте кода ), орбитер привязывается к этой SpatialPanel
. - В
Subspace
орбитер привязывается к ближайшей родительской сущности, которая представляет собой пространственную компоновку, в которой объявлен орбитер.
В следующем примере показано, как прикрепить орбитальный аппарат к пространственному ряду:
Subspace {
SpatialRow {
Orbiter(
position = ContentEdge.Top,
offset = 8.dp,
offsetType = OrbiterOffsetType.InnerEdge,
shape = SpatialRoundedCornerShape(size = CornerSize(50))
) {
Text(
"Hello World!",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.background(Color.White)
.padding(16.dp)
)
}
SpatialPanel(
SubspaceModifier
.height(824.dp)
.width(1400.dp)
) {
Box(
modifier = Modifier
.background(Color.Red)
)
}
SpatialPanel(
SubspaceModifier
.height(824.dp)
.width(1400.dp)
) {
Box(
modifier = Modifier
.background(Color.Blue)
)
}
}
}
Ключевые моменты кода
- При объявлении орбитера вне 2D-макета он привязывается к ближайшему родительскому объекту. В этом случае орбитер привязывается к верхней части
SpatialRow
, в котором он объявлен. - Пространственные макеты, такие как
SpatialRow
, SpatialColumn
, SpatialBox
, имеют связанные с ними сущности без содержимого. Поэтому орбитер, объявленный в пространственном макете, привязывается к этому макету.
Смотрите также