Разработка пользовательского интерфейса с помощью 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()
      }
}

// 2D content placed within the spatial panel
@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
        )
    }
}

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

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

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

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

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

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

//2D content inside Orbiter
@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) {
    val xrCoreSession = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    if (show3DObject) {
        Subspace {
            Volume(
                modifier = SubspaceModifier
                    .offset(volumeXOffset, volumeYOffset, volumeZOffset) //
Relative position
                    .scale(1.2f) // Scale to 120% of the size

            ) { parent ->
                scope.launch {
                   // Load your 3D Object here
                }
            }
        }
    }
}

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

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

Компоненты пространственного пользовательского интерфейса можно размещать в любом месте иерархии пользовательского интерфейса вашего приложения. Эти элементы можно повторно использовать в вашем 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) {
       Handler(Looper.getMainLooper()).postDelayed({
           showDialog = true
       }, 3000)
   }
   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.titleLarge,
                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 имеют связанные с ними бессодержательные объекты. Таким образом, орбитальный аппарат, объявленный в пространственном макете, привязывается к этому макету.

См. также