Разработка пользовательского интерфейса с помощью Jetpack Compose для XR

С помощью Jetpack Compose для XR вы можете декларативно создавать пространственный пользовательский интерфейс и макет, используя знакомые концепции Compose, такие как строки и столбцы. Это позволяет вам расширить существующий пользовательский интерфейс Android в 3D-пространстве или создавать совершенно новые иммерсивные 3D-приложения.

Если вы занимаетесь пространственным определением существующего приложения на базе Android Views, у вас есть несколько вариантов разработки. Вы можете использовать API-интерфейсы совместимости, использовать Compose и Views вместе или работать напрямую с библиотекой SceneCore. Более подробную информацию можно найти в нашем руководстве по работе с представлениями .

О подпространствах и пространственных компонентах

Когда вы пишете приложение для Android XR, важно понимать концепции подпространства и пространственных компонентов .

О подпространстве

При разработке для Android XR вам потребуется добавить подпространство в свое приложение или макет. Подпространство — это раздел 3D-пространства внутри вашего приложения, где вы можете размещать 3D-контент, создавать 3D-макеты и добавлять глубину к 2D-контенту. Подпространство визуализируется только тогда, когда включена пространственная обработка. В домашнем пространстве или на устройствах, отличных от XR, любой код в этом подпространстве игнорируется.

Существует два способа создания подпространства:

  • setSubspaceContent() : эта функция создает подпространство уровня приложения. Это можно вызвать в вашей основной деятельности так же, как вы используете setContent() . Подпространство уровня приложения не ограничено по высоте, ширине и глубине, по сути предоставляя бесконечный холст для пространственного контента.
  • Subspace : этот составной элемент можно разместить в любом месте иерархии пользовательского интерфейса вашего приложения, что позволяет вам поддерживать макеты для 2D- и пространственного пользовательского интерфейса без потери контекста между файлами. Это упрощает совместное использование таких вещей, как существующая архитектура приложения, между XR и другими форм-факторами без необходимости поднимать состояние по всему дереву пользовательского интерфейса или перепроектировать свое приложение.

Дополнительную информацию см. в разделе Добавление подпространства в ваше приложение .

О пространственных компонентах

Составные части подпространства : эти компоненты можно визуализировать только в подпространстве. Прежде чем помещать их в 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
        )
    }
}

Ключевые моменты о коде

Как работает модификатор подвижного подпространства

Когда пользователь перемещает панель от себя, модификатор movable subspace по умолчанию масштабирует панель аналогично тому, как система изменяет размер панелей в домашнем пространстве . Все дочерние элементы наследуют это поведение. Чтобы отключить это, установите для параметра scaleWithDistance значение false .

Создать орбитальный аппарат

Орбитальный аппарат — это пространственный компонент пользовательского интерфейса. Он предназначен для прикрепления к соответствующей пространственной панели, макету или другому объекту. Орбитальный аппарат обычно содержит элементы навигации и контекстных действий, связанные с объектом, к которому он привязан. Например, если вы создали пространственную панель для отображения видеоконтента, вы можете добавить элементы управления воспроизведением видео внутри орбитального аппарата.

Пример орбитального аппарата

Как показано в следующем примере, вызовите орбитальный аппарат внутри 2D-макета в SpatialPanel , чтобы обернуть пользовательские элементы управления, такие как навигация. При этом они извлекаются из вашего 2D-макета и прикрепляются к пространственной панели в соответствии с вашей конфигурацией.

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp)
            .movable()
            .resizable()
    ) {
        SpatialPanelContent()
        OrbiterExample()
    }
}

@Composable
fun OrbiterExample() {
    Orbiter(
        position = OrbiterEdge.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
        )
    }
}

Ключевые моменты о коде

  • SpatialRow , SpatialColumn , SpatialBox и SpatialLayoutSpacer являются компонуемыми подпространствами и должны быть размещены внутри подпространства.
  • Используйте SubspaceModifier для настройки макета.
  • Для макетов с несколькими панелями подряд мы рекомендуем установить радиус кривой 825dp с помощью SubspaceModifier , чтобы панели окружали пользователя. Подробности смотрите в нашем руководстве по проектированию .

Используйте объем для размещения 3D-объекта в макете.

Чтобы разместить 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,
            )
        }
    }
}

@Composable
fun ObjectInAVolume(show3DObject: Boolean) {

Дополнительная информация

Добавьте поверхность для изображения или видеоконтента

SpatialExternalSurface — это компонуемое подпространство, которое создает Surface и управляет ею, в которую ваше приложение может отображать контент, например изображение или видео . SpatialExternalSurface поддерживает стереоскопическое или моноскопическое содержимое.

В этом примере показано, как загрузить параллельное стереоскопическое видео с помощью Media3 Exoplayer и SpatialExternalSurface :

@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 с рендерингом приложения или декодированием видео.
  • Этот составной элемент не может отображаться перед другими панелями, поэтому не следует использовать подвижные модификаторы, если в макете есть другие панели.

Добавьте другие компоненты пространственного пользовательского интерфейса.

Компоненты пространственного пользовательского интерфейса можно размещать в любом месте иерархии пользовательского интерфейса вашего приложения. Эти элементы можно повторно использовать в вашем 2D-интерфейсе, а их пространственные атрибуты будут видны только при включении пространственных возможностей. Это позволяет вам добавлять высоту к меню, диалоговым окнам и другим компонентам без необходимости писать код дважды. См. следующие примеры пространственного пользовательского интерфейса, чтобы лучше понять, как использовать эти элементы.

Компонент пользовательского интерфейса

Когда пространственное определение включено

В 2D-среде

SpatialDialog

Панель слегка отодвинется назад по оси Z, чтобы отобразить диалоговое окно с повышенными правами.

Возвращается к 2D- Dialog .

SpatialPopup

Панель слегка отодвинется назад по оси Z, чтобы отобразить приподнятое всплывающее окно.

Возвращается к 2D Popup .

SpatialElevation

SpatialElevationLevel можно установить для добавления высоты.

Показывает без пространственной возвышенности.

Пространственный Диалог

Это пример диалогового окна, которое открывается после небольшой задержки. При использовании SpatialDialog диалоговое окно отображается на той же глубине по оси Z, что и пространственная панель, а панель отодвигается на 125dp, когда пространственная обработка включена. SpatialDialog также можно использовать, когда пространственная обработка не включена, и в этом случае SpatialDialog возвращается к своему 2D-аналогу 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 для XR, вы можете работать напрямую с PanelEntities и графом сцены, используя API-интерфейсы SceneCore .

Привязывайте орбитальные аппараты к пространственным макетам и другим объектам.

Вы можете привязать орбитальный аппарат к любому объекту, объявленному в Compose. Это включает в себя объявление орбитального аппарата в пространственной компоновке элементов пользовательского интерфейса, таких как SpatialRow , SpatialColumn или SpatialBox . Орбитальный аппарат привязывается к родительскому объекту, ближайшему к тому месту, где вы его объявили.

Поведение орбитального аппарата определяется тем, где вы его объявляете:

  • В 2D-макете, завернутом в SpatialPanel (как показано в предыдущем фрагменте кода ), орбитальный аппарат привязывается к этому SpatialPanel .
  • В Subspace орбитальный аппарат привязывается к ближайшему родительскому объекту, который представляет собой пространственный макет, в котором объявлен орбитальный аппарат.

В следующем примере показано, как привязать орбитальный аппарат к пространственной строке:

Subspace {
    SpatialRow {
        Orbiter(
            position = OrbiterEdge.Top,
            offset = EdgeOffset.inner(8.dp),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.h2,
                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 , имеют связанные с ними бессодержательные объекты. Таким образом, орбитальный аппарат, объявленный в пространственном макете, привязывается к этому макету.

См. также

,

С помощью Jetpack Compose для XR вы можете декларативно создавать пространственный пользовательский интерфейс и макет, используя знакомые концепции Compose, такие как строки и столбцы. Это позволяет вам расширить существующий пользовательский интерфейс Android в 3D-пространстве или создавать совершенно новые иммерсивные 3D-приложения.

Если вы занимаетесь пространственным определением существующего приложения на базе Android Views, у вас есть несколько вариантов разработки. Вы можете использовать API-интерфейсы совместимости, использовать Compose и Views вместе или работать напрямую с библиотекой SceneCore. Более подробную информацию можно найти в нашем руководстве по работе с представлениями .

О подпространствах и пространственных компонентах

Когда вы пишете приложение для Android XR, важно понимать концепции подпространства и пространственных компонентов .

О подпространстве

При разработке для Android XR вам потребуется добавить подпространство в свое приложение или макет. Подпространство — это раздел 3D-пространства внутри вашего приложения, где вы можете размещать 3D-контент, создавать 3D-макеты и добавлять глубину к 2D-контенту. Подпространство визуализируется только тогда, когда включена пространственная обработка. В домашнем пространстве или на устройствах, отличных от XR, любой код в этом подпространстве игнорируется.

Существует два способа создания подпространства:

  • setSubspaceContent() : эта функция создает подпространство уровня приложения. Это можно вызвать в вашей основной деятельности так же, как вы используете setContent() . Подпространство уровня приложения не ограничено по высоте, ширине и глубине, по сути предоставляя бесконечный холст для пространственного контента.
  • Subspace : этот составной элемент можно разместить в любом месте иерархии пользовательского интерфейса вашего приложения, что позволяет вам поддерживать макеты для 2D- и пространственного пользовательского интерфейса без потери контекста между файлами. Это упрощает совместное использование таких вещей, как существующая архитектура приложения, между XR и другими форм-факторами без необходимости поднимать состояние по всему дереву пользовательского интерфейса или перепроектировать ваше приложение.

Дополнительную информацию см. в разделе Добавление подпространства в ваше приложение .

О пространственных компонентах

Составные части подпространства : эти компоненты можно визуализировать только в подпространстве. Прежде чем помещать их в 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
        )
    }
}

Ключевые моменты о коде

Как работает модификатор подвижного подпространства

Когда пользователь перемещает панель от себя, модификатор movable subspace по умолчанию масштабирует панель аналогично тому, как система изменяет размер панелей в домашнем пространстве . Все дочерние элементы наследуют это поведение. Чтобы отключить это, установите для параметра scaleWithDistance значение false .

Создать орбитальный аппарат

Орбитальный аппарат — это пространственный компонент пользовательского интерфейса. Он предназначен для прикрепления к соответствующей пространственной панели, макету или другому объекту. Орбитальный аппарат обычно содержит элементы навигации и контекстных действий, связанные с объектом, к которому он привязан. Например, если вы создали пространственную панель для отображения видеоконтента, вы можете добавить элементы управления воспроизведением видео внутри орбитального аппарата.

Пример орбитального аппарата

Как показано в следующем примере, вызовите орбитальный аппарат внутри 2D-макета в SpatialPanel , чтобы обернуть пользовательские элементы управления, такие как навигация. При этом они извлекаются из вашего 2D-макета и прикрепляются к пространственной панели в соответствии с вашей конфигурацией.

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp)
            .movable()
            .resizable()
    ) {
        SpatialPanelContent()
        OrbiterExample()
    }
}

@Composable
fun OrbiterExample() {
    Orbiter(
        position = OrbiterEdge.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
        )
    }
}

Ключевые моменты о коде

  • SpatialRow , SpatialColumn , SpatialBox и SpatialLayoutSpacer являются компонуемыми подпространствами и должны быть размещены внутри подпространства.
  • Используйте SubspaceModifier для настройки макета.
  • Для макетов с несколькими панелями подряд мы рекомендуем установить радиус кривой 825dp с помощью SubspaceModifier , чтобы панели окружали пользователя. Подробности смотрите в нашем руководстве по проектированию .

Используйте объем для размещения 3D-объекта в макете.

Чтобы разместить 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,
            )
        }
    }
}

@Composable
fun ObjectInAVolume(show3DObject: Boolean) {

Дополнительная информация

Добавьте поверхность для изображения или видеоконтента

SpatialExternalSurface — это компонуемое подпространство, которое создает Surface и управляет ею, в которую ваше приложение может отображать контент, например изображение или видео . SpatialExternalSurface поддерживает стереоскопическое или моноскопическое содержимое.

В этом примере показано, как загрузить параллельное стереоскопическое видео с помощью Media3 Exoplayer и SpatialExternalSurface :

@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 с рендерингом приложения или декодированием видео.
  • Этот составной элемент не может отображаться перед другими панелями, поэтому не следует использовать подвижные модификаторы, если в макете есть другие панели.

Добавьте другие компоненты пространственного пользовательского интерфейса.

Компоненты пространственного пользовательского интерфейса можно размещать в любом месте иерархии пользовательского интерфейса вашего приложения. Эти элементы можно повторно использовать в вашем 2D-интерфейсе, а их пространственные атрибуты будут видны только при включении пространственных возможностей. Это позволяет вам добавлять высоту к меню, диалоговым окнам и другим компонентам без необходимости писать код дважды. См. следующие примеры пространственного пользовательского интерфейса, чтобы лучше понять, как использовать эти элементы.

Компонент пользовательского интерфейса

Когда пространственное определение включено

В 2D-среде

SpatialDialog

Панель слегка отодвинется назад по оси Z, чтобы отобразить диалоговое окно с повышенными правами.

Возвращается к 2D- Dialog .

SpatialPopup

Панель слегка отодвинется назад по оси Z, чтобы отобразить приподнятое всплывающее окно.

Возвращается к 2D Popup .

SpatialElevation

SpatialElevationLevel можно установить для добавления высоты.

Показывает без пространственной возвышенности.

Пространственный Диалог

Это пример диалогового окна, которое открывается после небольшой задержки. При использовании SpatialDialog диалоговое окно отображается на той же глубине по оси Z, что и пространственная панель, а панель отодвигается на 125dp, когда пространственная обработка включена. SpatialDialog также можно использовать, когда пространственная обработка не включена, и в этом случае SpatialDialog возвращается к своему 2D-аналогу 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 для XR, вы можете работать напрямую с PanelEntities и графом сцены, используя API-интерфейсы SceneCore .

Привязывайте орбитальные аппараты к пространственным макетам и другим объектам.

Вы можете привязать орбитальный аппарат к любому объекту, объявленному в Compose. Это включает в себя объявление орбитального аппарата в пространственной компоновке элементов пользовательского интерфейса, таких как SpatialRow , SpatialColumn или SpatialBox . Орбитальный аппарат привязывается к родительскому объекту, ближайшему к тому месту, где вы его объявили.

Поведение орбитального аппарата определяется тем, где вы его объявляете:

  • В 2D-макете, завернутом в SpatialPanel (как показано в предыдущем фрагменте кода ), орбитальный аппарат привязывается к этому SpatialPanel .
  • В Subspace орбитальный аппарат привязывается к ближайшему родительскому объекту, который представляет собой пространственный макет, в котором объявлен орбитальный аппарат.

В следующем примере показано, как привязать орбитальный аппарат к пространственной строке:

Subspace {
    SpatialRow {
        Orbiter(
            position = OrbiterEdge.Top,
            offset = EdgeOffset.inner(8.dp),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.h2,
                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 , имеют связанные с ними бессодержательные объекты. Таким образом, орбитальный аппарат, объявленный в пространственном макете, привязывается к этому макету.

См. также

,

С помощью Jetpack Compose для XR вы можете декларативно создавать пространственный пользовательский интерфейс и макет, используя знакомые концепции Compose, такие как строки и столбцы. Это позволяет вам расширить существующий пользовательский интерфейс Android в 3D-пространстве или создавать совершенно новые иммерсивные 3D-приложения.

Если вы занимаетесь пространственным определением существующего приложения на базе Android Views, у вас есть несколько вариантов разработки. Вы можете использовать API-интерфейсы совместимости, использовать Compose и Views вместе или работать напрямую с библиотекой SceneCore. Более подробную информацию можно найти в нашем руководстве по работе с представлениями .

О подпространствах и пространственных компонентах

Когда вы пишете приложение для Android XR, важно понимать концепции подпространства и пространственных компонентов .

О подпространстве

При разработке для Android XR вам потребуется добавить подпространство в ваше приложение или макет. Подпространство — это раздел 3D-пространства внутри вашего приложения, где вы можете размещать 3D-контент, создавать 3D-макеты и добавлять глубину к 2D-контенту. Подпространство визуализируется только тогда, когда включена пространственная обработка. В домашнем пространстве или на устройствах, отличных от XR, любой код в этом подпространстве игнорируется.

Существует два способа создания подпространства:

  • setSubspaceContent() : эта функция создает подпространство уровня приложения. Это можно вызвать в вашей основной деятельности так же, как вы используете setContent() . Подпространство уровня приложения не ограничено по высоте, ширине и глубине, по сути предоставляя бесконечный холст для пространственного контента.
  • Subspace : этот составной элемент можно разместить в любом месте иерархии пользовательского интерфейса вашего приложения, что позволяет вам поддерживать макеты для 2D- и пространственного пользовательского интерфейса без потери контекста между файлами. Это упрощает совместное использование таких вещей, как существующая архитектура приложения, между XR и другими форм-факторами без необходимости поднимать состояние по всему дереву пользовательского интерфейса или перепроектировать ваше приложение.

Дополнительную информацию см. в разделе Добавление подпространства в ваше приложение .

О пространственных компонентах

Составные части подпространства : эти компоненты можно визуализировать только в подпространстве. Прежде чем помещать их в 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
        )
    }
}

Ключевые моменты о коде

Как работает модификатор подвижного подпространства

Когда пользователь перемещает панель от себя, модификатор movable subspace по умолчанию масштабирует панель аналогично тому, как система изменяет размер панелей в домашнем пространстве . Все дочерние элементы наследуют это поведение. Чтобы отключить это, установите для параметра scaleWithDistance значение false .

Создать орбитальный аппарат

Орбитальный аппарат — это пространственный компонент пользовательского интерфейса. Он предназначен для прикрепления к соответствующей пространственной панели, макету или другому объекту. Орбитальный аппарат обычно содержит элементы навигации и контекстных действий, связанные с объектом, к которому он привязан. Например, если вы создали пространственную панель для отображения видеоконтента, вы можете добавить элементы управления воспроизведением видео внутри орбитального аппарата.

Пример орбитального аппарата

Как показано в следующем примере, вызовите орбитальный аппарат внутри 2D-макета в SpatialPanel , чтобы обернуть пользовательские элементы управления, такие как навигация. При этом они извлекаются из вашего 2D-макета и прикрепляются к пространственной панели в соответствии с вашей конфигурацией.

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp)
            .movable()
            .resizable()
    ) {
        SpatialPanelContent()
        OrbiterExample()
    }
}

@Composable
fun OrbiterExample() {
    Orbiter(
        position = OrbiterEdge.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
        )
    }
}

Ключевые моменты о коде

  • SpatialRow , SpatialColumn , SpatialBox и SpatialLayoutSpacer являются компонуемыми подпространствами и должны быть размещены внутри подпространства.
  • Используйте SubspaceModifier для настройки макета.
  • Для макетов с несколькими панелями подряд мы рекомендуем установить радиус кривой 825dp с помощью SubspaceModifier , чтобы панели окружали пользователя. Подробности смотрите в нашем руководстве по проектированию .

Используйте объем для размещения 3D-объекта в макете.

Чтобы разместить 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,
            )
        }
    }
}

@Composable
fun ObjectInAVolume(show3DObject: Boolean) {

Дополнительная информация

Добавьте поверхность для изображения или видеоконтента

SpatialExternalSurface — это компонуемое подпространство, которое создает Surface и управляет ею, в которую ваше приложение может отображать контент, например изображение или видео . SpatialExternalSurface поддерживает стереоскопическое или моноскопическое содержимое.

В этом примере показано, как загрузить параллельное стереоскопическое видео с помощью Media3 Exoplayer и SpatialExternalSurface :

@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 с рендерингом приложения или декодированием видео.
  • Этот составной элемент не может отображаться перед другими панелями, поэтому не следует использовать подвижные модификаторы, если в макете есть другие панели.

Добавьте другие компоненты пространственного пользовательского интерфейса.

Компоненты пространственного пользовательского интерфейса можно размещать в любом месте иерархии пользовательского интерфейса вашего приложения. Эти элементы можно повторно использовать в вашем 2D-интерфейсе, а их пространственные атрибуты будут видны только при включении пространственных возможностей. Это позволяет вам добавлять высоту к меню, диалоговым окнам и другим компонентам без необходимости писать код дважды. См. следующие примеры пространственного пользовательского интерфейса, чтобы лучше понять, как использовать эти элементы.

Компонент пользовательского интерфейса

Когда пространственное определение включено

В 2D-среде

SpatialDialog

Панель слегка отодвинется назад по оси Z, чтобы отобразить диалоговое окно с повышенными правами.

Возвращается к 2D- Dialog .

SpatialPopup

Панель слегка отодвинется назад по оси Z, чтобы отобразить приподнятое всплывающее окно.

Возвращается к 2D Popup .

SpatialElevation

SpatialElevationLevel можно установить для добавления высоты.

Показывает без пространственной возвышенности.

Пространственный Диалог

Это пример диалогового окна, которое открывается после небольшой задержки. При использовании SpatialDialog диалоговое окно отображается на той же глубине по оси Z, что и пространственная панель, а панель отодвигается на 125dp, когда пространственная обработка включена. SpatialDialog также можно использовать, когда пространственная обработка не включена, и в этом случае SpatialDialog возвращается к своему 2D-аналогу 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 для XR, вы можете работать напрямую с PanelEntities и графом сцены, используя API-интерфейсы SceneCore .

Привязывайте орбитальные аппараты к пространственным макетам и другим объектам.

Вы можете привязать орбитальный аппарат к любому объекту, объявленному в Compose. Это включает в себя объявление орбитального аппарата в пространственной компоновке элементов пользовательского интерфейса, таких как SpatialRow , SpatialColumn или SpatialBox . Орбитальный аппарат привязывается к родительскому объекту, ближайшему к тому месту, где вы его объявили.

Поведение орбитального аппарата определяется тем, где вы его объявляете:

  • В 2D-макете, завернутом в SpatialPanel (как показано в предыдущем фрагменте кода ), орбитальный аппарат привязывается к этому SpatialPanel .
  • В Subspace орбитальный аппарат привязывается к ближайшему родительскому объекту, который представляет собой пространственный макет, в котором объявлен орбитальный аппарат.

В следующем примере показано, как привязать орбитальный аппарат к пространственной строке:

Subspace {
    SpatialRow {
        Orbiter(
            position = OrbiterEdge.Top,
            offset = EdgeOffset.inner(8.dp),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.h2,
                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 , имеют связанные с ними бессодержательные объекты. Таким образом, орбитальный аппарат, объявленный в пространственном макете, привязывается к этому макету.

См. также

,

С помощью Jetpack Compose для XR вы можете декларативно создавать пространственный пользовательский интерфейс и макет, используя знакомые концепции Compose, такие как строки и столбцы. Это позволяет вам расширить существующий пользовательский интерфейс Android в 3D-пространстве или создавать совершенно новые иммерсивные 3D-приложения.

Если вы занимаетесь пространственным определением существующего приложения на базе Android Views, у вас есть несколько вариантов разработки. Вы можете использовать API-интерфейсы совместимости, использовать Compose и Views вместе или работать напрямую с библиотекой SceneCore. Более подробную информацию можно найти в нашем руководстве по работе с представлениями .

О подпространствах и пространственных компонентах

Когда вы пишете приложение для Android XR, важно понимать концепции подпространства и пространственных компонентов .

О подпространстве

При разработке для Android XR вам потребуется добавить подпространство в свое приложение или макет. Подпространство — это раздел 3D-пространства внутри вашего приложения, где вы можете размещать 3D-контент, создавать 3D-макеты и добавлять глубину к 2D-контенту. Подпространство визуализируется только тогда, когда включена пространственная обработка. В домашнем пространстве или на устройствах, отличных от XR, любой код в этом подпространстве игнорируется.

Существует два способа создания подпространства:

  • setSubspaceContent() : эта функция создает подпространство уровня приложения. Это можно вызвать в вашей основной деятельности так же, как вы используете setContent() . Подпространство уровня приложения не ограничено по высоте, ширине и глубине, по сути предоставляя бесконечный холст для пространственного контента.
  • Subspace : этот составной элемент можно разместить в любом месте иерархии пользовательского интерфейса вашего приложения, что позволяет вам поддерживать макеты для 2D- и пространственного пользовательского интерфейса без потери контекста между файлами. Это упрощает совместное использование таких вещей, как существующая архитектура приложения, между XR и другими форм-факторами без необходимости поднимать состояние по всему дереву пользовательского интерфейса или перепроектировать ваше приложение.

Дополнительную информацию см. в разделе Добавление подпространства в ваше приложение .

О пространственных компонентах

Подпространственные композиции : эти компоненты могут быть отображены только в подпространстве. Они должны быть заключены в Subspace или setSubspaceContent прежде чем помещать в 2D -макет. 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
        )
    }
}

Ключевые моменты о коде

Как работает подвижный модификатор подпространства

По мере того, как пользователь отодвигает панель от них, по умолчанию подвижный модификатор подпространства масштабирует панель аналогично тому, как панели изменяются по системе в домашнем пространстве . Все дети содержат наследуют это поведение. Чтобы отключить это, установите параметр scaleWithDistance на false .

Создать орбитаж

Орбитатор - это пространственный компонент пользовательского интерфейса. Он предназначен для прикрепления к соответствующей пространственной панели, макете или другой сущности. Орбитатор обычно содержит навигационные и контекстуальные элементы действий, связанные с сущностью, к которой он прикреплен. Например, если вы создали пространственную панель для отображения видеоконтента, вы можете добавить элементы управления воспроизведением видео на орбитальный аппарат.

Пример орбитального отверстия

Как показано в следующем примере, вызовите орбитаж внутри 2D -макета в SpatialPanel , чтобы обернуть элементы управления пользователями, такие как навигация. Это извлекает их из вашего 2D -макета и прикрепляет их к пространственной панели в соответствии с вашей конфигурацией.

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp)
            .movable()
            .resizable()
    ) {
        SpatialPanelContent()
        OrbiterExample()
    }
}

@Composable
fun OrbiterExample() {
    Orbiter(
        position = OrbiterEdge.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
        )
    }
}

Ключевые моменты о коде

  • SpatialRow , SpatialColumn , SpatialBox и SpatialLayoutSpacer - все подпространство и должны быть размещены в подпространстве.
  • Используйте SubspaceModifier , чтобы настроить макет.
  • Для макетов с несколькими панелями подряд мы рекомендуем установить радиус кривой 825DP, используя SubspaceModifier , чтобы панели окружали вашего пользователя. Смотрите наше руководство по дизайну для деталей.

Используйте объем, чтобы поместить 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,
            )
        }
    }
}

@Composable
fun ObjectInAVolume(show3DObject: Boolean) {

Дополнительная информация

Добавить поверхность для изображения или видеоконтента

SpatialExternalSurface - это подпространство, которое создает и управляет Surface , на которую ваше приложение может рисовать контент, например, изображение или видео . SpatialExternalSurface поддерживает либо стереоскопическое, либо моноскопическое содержание.

Этот пример демонстрирует, как загрузить стереоскопическое видео с использованием Media3 Exoplayer и SpatialExternalSurface :

@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 с рендерингом приложений или декодированием видео.
  • Этот композиционный не может рендеринг перед другими панелями, поэтому вы не должны использовать подвижные модификаторы, если в макете есть другие панели.

Добавить другие пространственные компоненты пользовательского интерфейса

Пространственные компоненты пользовательского интерфейса могут быть размещены в любом месте иерархии пользовательского интерфейса вашего приложения. Эти элементы могут быть повторно использованы в вашем 2D -интерфейсе, и их пространственные атрибуты будут видны только тогда, когда будут включены пространственные возможности. Это позволяет добавлять высоту в меню, диалоги и другие компоненты без необходимости писать ваш код дважды. См. Следующие примеры пространственного пользовательского интерфейса, чтобы лучше понять, как использовать эти элементы.

Компонент пользовательского интерфейса

Когда пространственная пространство включена

В 2D средах

SpatialDialog

Панель будет слегка натолкнуться в глубину Z, чтобы отобразить повышенный диалог

Возвращается к 2D Dialog .

SpatialPopup

Панель будет слегка натолкнуться назад в глубину Z, чтобы отобразить повышенное всплывающее окно

Возвращается к 2D -всплывающему Popup .

SpatialElevation

SpatialElevationLevel может быть установлен для добавления возвышения.

Показывает без пространственного возвышения.

Spatialdialog

Это пример диалога, который открывается после короткой задержки. Когда используется SpatialDialog , диалог появляется на том же Z-глубине, что и пространственная панель, и панель отталкивается на 125DP, когда включена пространственная пространственная способность. SpatialDialog также может использоваться, когда пространственная пространственная система не включена, и в этом случае SpatialDialog возвращается к своему 2D 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 для XR, вы можете работать непосредственно с PanelEntities и графом сцены, используя API -интерфейсы SceneCore .

Якоря орбитальных операций на пространственные макеты и другие сущности

Вы можете прикрепить орбитаж на любую организацию, объявленную в Compose. Это включает в себя объявление орбитального отверстия в пространственном расположении элементов пользовательского интерфейса, таких как SpatialRow , SpatialColumn или SpatialBox . Орбитатор прикрепляет к родительскому сущности, ближайшей к тому месту, где вы это объявили.

Поведение орбитаря определяется тем, где вы его объявляете:

  • В 2D -макете, завернутой в SpatialPanel (как показано в предыдущем фрагменте кода ), орбитальные операции привязаны к этой SpatialPanel .
  • В Subspace орбитальные отверстия привязаны к ближайшему родительскому сущности, которая является пространственной планировкой, в которой объявляется орбитаж.

В следующем примере показано, как закрепить орбитаж на пространственную строку:

Subspace {
    SpatialRow {
        Orbiter(
            position = OrbiterEdge.Top,
            offset = EdgeOffset.inner(8.dp),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.h2,
                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 имеют без контента, связанные с ними. Следовательно, орбитальный оператор объявил в пространственном макете привязки к этой макету.

См. также