พัฒนา UI ด้วย Jetpack Compose สำหรับ XR

Jetpack Compose สำหรับ XR ช่วยให้คุณสร้าง UI เชิงพื้นที่และเลย์เอาต์ได้โดยใช้แนวคิด Compose ที่คุ้นเคย เช่น แถวและคอลัมน์ ซึ่งช่วยให้คุณ ขยาย UI ของ Android ที่มีอยู่ไปยังพื้นที่ 3 มิติ หรือสร้างแอปพลิเคชัน 3 มิติที่สมจริงใหม่ทั้งหมดได้

หากต้องการทำให้แอปที่มีอยู่ซึ่งอิงตาม Android Views มีมิติเชิงพื้นที่ คุณมีตัวเลือกการพัฒนาหลายอย่าง คุณสามารถใช้ API การทำงานร่วมกัน ใช้ Compose และ Views ร่วมกัน หรือทำงานกับไลบรารี SceneCore โดยตรง ดูรายละเอียดเพิ่มเติมได้ในคำแนะนำในการทำงาน กับมุมมอง

เกี่ยวกับพื้นที่ย่อยและคอมโพเนนต์เชิงพื้นที่

เมื่อเขียนแอปสำหรับ Android XR คุณควรทำความเข้าใจแนวคิดของพื้นที่ย่อยและคอมโพเนนต์เชิงพื้นที่

เกี่ยวกับพื้นที่ย่อย

เมื่อพัฒนาสำหรับ Android XR คุณจะต้องเพิ่ม Subspace ลงในแอป หรือเลย์เอาต์ Subspace คือพาร์ติชันของพื้นที่ 3 มิติภายในแอปที่คุณสามารถ วางเนื้อหา 3 มิติ สร้างเลย์เอาต์ 3 มิติ และเพิ่มมิติให้กับเนื้อหา 2 มิติ ระบบจะแสดง พื้นที่ย่อยเมื่อเปิดใช้การปรับเสียงตามพื้นที่เท่านั้น ในพื้นที่บ้านหรือในอุปกรณ์ที่ไม่ใช่ XR ระบบจะไม่สนใจโค้ดใดๆ ภายในพื้นที่ย่อยนั้น

คุณสร้างพื้นที่ย่อยได้ 2 วิธีดังนี้

  • Subspace: วาง Composable นี้ได้ทุกที่ภายในลำดับชั้น UI ของแอป ซึ่งจะช่วยให้คุณรักษาเลย์เอาต์สำหรับ UI 2 มิติและเชิงพื้นที่ได้โดยไม่ สูญเสียบริบทระหว่างไฟล์ ซึ่งจะช่วยให้แชร์สิ่งต่างๆ เช่น สถาปัตยกรรมของแอปที่มีอยู่ระหว่าง XR กับอุปกรณ์รูปแบบอื่นๆ ได้ง่ายขึ้นโดยไม่ต้อง ยกสถานะผ่านทั้งโครงสร้าง UI หรือออกแบบแอปใหม่
  • ApplicationSubspace: ฟังก์ชันนี้จะสร้างพื้นที่ย่อยระดับแอปเท่านั้น และต้องวางไว้ที่ระดับบนสุดในลำดับชั้นของ UI เชิงพื้นที่ของแอปพลิเคชัน ApplicationSubspace แสดงเนื้อหาเชิงพื้นที่พร้อมกับVolumeConstraints (ไม่บังคับ) Subspace ต่างจาก ApplicationSubspace ตรงที่ซ้อนภายใน Subspace หรือ ApplicationSubspace ไม่ได้

ดูข้อมูลเพิ่มเติมได้ที่เพิ่มพื้นที่ย่อยลงในแอป

เกี่ยวกับคอมโพเนนต์เชิงพื้นที่

Composable ของ Subspace: คอมโพเนนต์เหล่านี้จะแสดงผลได้ใน Subspace เท่านั้น โดยต้องอยู่ภายใน Subspace ก่อนที่จะวางภายในเลย์เอาต์ 2 มิติ SubspaceModifier ช่วยให้คุณเพิ่มแอตทริบิวต์ต่างๆ เช่น ความลึก ออฟเซ็ต และ การวางตำแหน่ง ลงใน Composable ของ Subspace ได้

ส่วนประกอบอื่นๆ ที่มีมิติเชิงพื้นที่ไม่จำเป็นต้องเรียกใช้ภายในพื้นที่ย่อย โดยประกอบด้วยองค์ประกอบ 2 มิติแบบเดิมที่ห่อหุ้มอยู่ภายในคอนเทนเนอร์เชิงพื้นที่ องค์ประกอบเหล่านี้สามารถใช้ภายในเลย์เอาต์ 2 มิติหรือ 3 มิติได้หากกำหนดไว้สำหรับทั้ง 2 อย่าง เมื่อไม่ได้เปิดใช้ การปรับเสียงตามตำแหน่ง ระบบจะไม่สนใจฟีเจอร์ที่ปรับเสียงตามตำแหน่งของเสียงเหล่านั้น และ จะกลับไปใช้เสียง 2 มิติแทน

สร้างแผงเชิงพื้นที่

SpatialPanel คือพื้นที่ย่อยที่ประกอบได้ซึ่งช่วยให้คุณแสดงเนื้อหาแอป ได้ เช่น คุณอาจแสดงการเล่นวิดีโอ ภาพนิ่ง หรือเนื้อหาอื่นๆ ในแผงเชิงพื้นที่

ตัวอย่างแผง UI เชิงพื้นที่

คุณใช้ SubspaceModifier เพื่อเปลี่ยนขนาด ลักษณะการทำงาน และตำแหน่งของ แผงเชิงพื้นที่ได้ ดังตัวอย่างต่อไปนี้

Subspace {     SpatialPanel(         SubspaceModifier             .height(824.dp)             .width(1400.dp),         dragPolicy = MovePolicy(),         resizePolicy = ResizePolicy(),     ) {         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 Composables คุณจึงต้องเรียกใช้ API เหล่านี้ภายใน Subspace การเรียกใช้ฟังก์ชันเหล่านี้นอก Subspace จะทำให้เกิดข้อยกเว้น
  • ระบบได้ตั้งค่าขนาดของ SpatialPanel โดยใช้ข้อกำหนด height และ width ใน SubspaceModifier การละเว้นข้อกำหนดเหล่านี้จะทำให้ ระบบกำหนดขนาดของแผงตามการวัดเนื้อหาของแผง
  • อนุญาตให้ผู้ใช้ย้ายแผงโดยการเพิ่ม MovePolicy
  • อนุญาตให้ผู้ใช้ปรับขนาดแผงโดยเพิ่ม ResizePolicy
  • ดูรายละเอียดเกี่ยวกับการปรับขนาดและการวางตำแหน่งได้ในคำแนะนำการออกแบบแผงเชิงพื้นที่ ดูรายละเอียดเพิ่มเติมเกี่ยวกับการติดตั้งโค้ดได้ในเอกสารประกอบอ้างอิง

วิธีการทำงานของ MovePolicy

เมื่อผู้ใช้เลื่อนแผงออกจากตัว ตามค่าเริ่มต้น MovePolicy จะปรับขนาดแผงในลักษณะเดียวกับที่ระบบปรับขนาดแผงในพื้นที่บ้าน เนื้อหาทั้งหมดสำหรับเด็กจะรับช่วงลักษณะการทำงานนี้ หากต้องการปิดใช้ ให้ตั้งค่าพารามิเตอร์ shouldScaleWithDistance เป็น false

สร้างยานโคจร

Orbiter เป็นคอมโพเนนต์ UI เชิงพื้นที่ โดยออกแบบมาให้แนบกับ แผงเชิงพื้นที่ เลย์เอาต์ หรือเอนทิตีอื่นๆ ที่เกี่ยวข้อง โดยปกติแล้ว ออบิเตอร์จะมี รายการการนำทางและการดำเนินการตามบริบทที่เกี่ยวข้องกับเอนทิตีที่ออบิเตอร์ ยึดไว้ เช่น หากคุณสร้างแผงเชิงพื้นที่เพื่อแสดงเนื้อหาวิดีโอ คุณก็เพิ่มตัวควบคุมการเล่นวิดีโอภายใน Orbiter ได้

ตัวอย่างยานโคจร

ดังที่แสดงในตัวอย่างต่อไปนี้ ให้เรียกใช้ Orbiter ภายในเลย์เอาต์ 2 มิติใน SpatialPanel เพื่อห่อหุ้มการควบคุมของผู้ใช้ เช่น การนำทาง การดำเนินการนี้จะดึงข้อมูลจากเลย์เอาต์ 2 มิติและแนบไปกับแผงเชิงพื้นที่ตามการกำหนดค่าของคุณ

Subspace {     SpatialPanel(         SubspaceModifier             .height(824.dp)             .width(1400.dp),         dragPolicy = MovePolicy(),         resizePolicy = ResizePolicy(),     ) {         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                 )             }         }     } }

ประเด็นสำคัญเกี่ยวกับโค้ด

  • เนื่องจากออบิเตอร์เป็นคอมโพเนนต์ UI เชิงพื้นที่ จึงสามารถนำโค้ดไปใช้ซ้ำในเลย์เอาต์ 2 มิติหรือ 3 มิติได้ ในเลย์เอาต์ 2 มิติ แอปจะแสดงเฉพาะเนื้อหาภายใน orbiter และไม่สนใจ orbiter เอง
  • ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้และออกแบบออร์บิทัลได้ในคำแนะนำด้านการออกแบบ

เพิ่มแผงเชิงพื้นที่หลายแผงไปยังเลย์เอาต์เชิงพื้นที่

คุณสร้างแผงเชิงพื้นที่ได้หลายแผงและวางไว้ในการจัดวางเชิงพื้นที่ โดยใช้ 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 เป็น Composable ของ Subspace ทั้งหมดและต้องวาง ภายใน Subspace
  • ใช้ SubspaceModifier เพื่อปรับแต่งเลย์เอาต์
  • สำหรับเลย์เอาต์ที่มีหลายแผงในแถว เราขอแนะนำให้ตั้งค่ารัศมีโค้งเป็น 825dp โดยใช้ SubspaceModifier เพื่อให้แผงล้อมรอบผู้ใช้ ดูรายละเอียดได้ในคำแนะนำด้านการออกแบบ

ใช้ปริมาตรเพื่อวางออบเจ็กต์ 3 มิติในเลย์เอาต์

หากต้องการวางออบเจ็กต์ 3 มิติในเลย์เอาต์ คุณจะต้องใช้ Subspace ที่ประกอบได้ ซึ่งเรียกว่าวอลุ่ม ตัวอย่างวิธีการมีดังนี้

ตัวอย่างออบเจ็กต์ 3 มิติในเลย์เอาต์

Subspace {     SpatialPanel(         SubspaceModifier.height(1500.dp).width(1500.dp),         dragPolicy = MovePolicy(),         resizePolicy = ResizePolicy(),     ) {         ObjectInAVolume(true)         Box(             Modifier.fillMaxSize(),             contentAlignment = Alignment.Center         ) {             Text(                 text = "Welcome",                 fontSize = 50.sp,             )         }     } }

@OptIn(ExperimentalSubspaceVolumeApi::class) @Composable fun ObjectInAVolume(show3DObject: Boolean) {     val session = 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 model here                 }             }         }     } }

ข้อมูลเพิ่มเติม

เพิ่มพื้นผิวสำหรับเนื้อหารูปภาพหรือวิดีโอ

SpatialExternalSurface คือ Subspace ที่สามารถคอมโพสได้ซึ่งสร้างและ จัดการ 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: เฟรมรูปภาพหรือวิดีโอประกอบด้วยรูปภาพเดียวที่เหมือนกัน ซึ่งแสดงต่อทั้ง 2 ตา
    • SideBySide: รูปภาพหรือเฟรมวิดีโอมีรูปภาพหรือเฟรมวิดีโอ 2 รายการที่จัดเรียงไว้ข้างกัน โดยรูปภาพหรือเฟรมทางด้านซ้ายแสดงมุมมองของตาซ้าย และรูปภาพหรือเฟรมทางด้านขวาแสดงมุมมองของตาขวา
    • TopBottom: เฟรมรูปภาพหรือวิดีโอมีรูปภาพหรือเฟรมวิดีโอ 2 รายการซ้อนกันในแนวตั้ง โดยรูปภาพหรือเฟรมที่อยู่ด้านบนแสดงมุมมองของตาซ้าย และรูปภาพหรือเฟรมที่อยู่ด้านล่างแสดงมุมมองของตาขวา
  • SpatialExternalSurface รองรับเฉพาะพื้นผิวสี่เหลี่ยมผืนผ้า
  • Surface นี้จะไม่บันทึกเหตุการณ์การป้อนข้อมูล
  • คุณไม่สามารถซิงค์การเปลี่ยนแปลง StereoMode กับการแสดงผลของแอปพลิเคชัน หรือการถอดรหัสวิดีโอ
  • Composable นี้แสดงผลอยู่หน้าแผงอื่นๆ ไม่ได้ คุณจึงไม่ควรใช้ MovePolicy หากมีแผงอื่นๆ ในเลย์เอาต์

เพิ่มแพลตฟอร์มสำหรับเนื้อหาวิดีโอที่ได้รับการคุ้มครองโดย DRM

SpatialExternalSurface ยังรองรับการเล่นวิดีโอสตรีมที่ได้รับการคุ้มครองโดย DRM ด้วย หากต้องการเปิดใช้ฟีเจอร์นี้ คุณต้องสร้างพื้นผิวที่ปลอดภัยซึ่งแสดงผลไปยังบัฟเฟอร์กราฟิกที่ได้รับการป้องกัน วิธีนี้จะช่วยป้องกันไม่ให้มีการบันทึกหน้าจอเนื้อหา หรือเข้าถึงเนื้อหาโดยคอมโพเนนต์ของระบบที่ไม่ปลอดภัย

หากต้องการสร้างพื้นผิวที่ปลอดภัย ให้ตั้งค่าพารามิเตอร์ surfaceProtection เป็น SurfaceProtection.Protected ใน Composable 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
  • เนื้อหาที่ปลอดภัย: เมื่อแสดงผลไปยังพื้นผิวที่ได้รับการป้องกัน ระบบจะถอดรหัสและแสดงเนื้อหาวิดีโอในเส้นทางที่ปลอดภัย ซึ่งจะช่วยให้เป็นไปตามข้อกำหนดในการอนุญาตให้ใช้เนื้อหา นอกจากนี้ ยังช่วยป้องกันไม่ให้เนื้อหาปรากฏในภาพหน้าจอด้วย

เพิ่มคอมโพเนนต์ UI เชิงพื้นที่อื่นๆ

วางคอมโพเนนต์ UI เชิงพื้นที่ไว้ที่ใดก็ได้ในลําดับชั้น UI ของแอปพลิเคชัน คุณสามารถนำองค์ประกอบเหล่านี้ไปใช้ซ้ำใน UI 2 มิติได้ และแอตทริบิวต์เชิงพื้นที่จะ แสดงเมื่อเปิดใช้ความสามารถเชิงพื้นที่เท่านั้น ซึ่งช่วยให้คุณเพิ่ม ระดับความสูงให้กับเมนู กล่องโต้ตอบ และคอมโพเนนต์อื่นๆ ได้โดยไม่ต้องเขียน โค้ดซ้ำ ดูตัวอย่างต่อไปนี้ของ UI เชิงพื้นที่เพื่อให้เข้าใจวิธี ใช้องค์ประกอบเหล่านี้ได้ดียิ่งขึ้น

คอมโพเนนต์ UI

เมื่อเปิดใช้การปรับเสียงตามตำแหน่ง

ในสภาพแวดล้อม 2 มิติ

SpatialDialog

แผงจะเลื่อนกลับไปเล็กน้อยในความลึกของแกน Z เพื่อแสดงกล่องโต้ตอบที่ยกระดับ

กลับไปเป็น 2 มิติ Dialog

SpatialPopup

แผงจะเลื่อนกลับไปเล็กน้อยในความลึกของแกน Z เพื่อแสดงป๊อปอัปที่ยกระดับ

กลับไปใช้ Popup แบบ 2 มิติ

SpatialElevation

SpatialElevationLevel สามารถตั้งค่าเพื่อเพิ่มระดับความสูงได้

รายการที่ไม่มีการยกระดับเชิงพื้นที่

SpatialDialog

นี่คือตัวอย่างกล่องโต้ตอบที่จะเปิดขึ้นหลังจากผ่านไปสักครู่ เมื่อใช้ SpatialDialog กล่องโต้ตอบจะปรากฏที่ความลึก z เดียวกับ แผงเชิงพื้นที่ และระบบจะเลื่อนแผงกลับไป 125dp เมื่อเปิดใช้การปรับเสียงตามพื้นที่ นอกจากนี้ คุณยังใช้ SpatialDialog ได้ด้วยเมื่อไม่ได้เปิดใช้การกำหนดตำแหน่งเสียง ในกรณีนี้ SpatialDialog จะกลับไปใช้ Dialog ซึ่งเป็นเวอร์ชัน 2 มิติ

@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 และกราฟฉากได้โดยตรงโดยใช้ SceneCore API

ยึดวงโคจรของ Anchor กับเลย์เอาต์เชิงพื้นที่และเอนทิตีอื่นๆ

คุณยึด Orbiter กับเอนทิตีใดก็ได้ที่ประกาศใน Compose ซึ่งเกี่ยวข้องกับการประกาศออบิเตอร์ในเลย์เอาต์เชิงพื้นที่ขององค์ประกอบ UI เช่น SpatialRow, SpatialColumn หรือ SpatialBox โดยวงโคจรจะยึดกับเอนทิตีหลัก ที่อยู่ใกล้กับตำแหน่งที่คุณประกาศมากที่สุด

ลักษณะการทำงานของวงโคจรจะขึ้นอยู่กับตำแหน่งที่คุณประกาศ ดังนี้

  • ในเลย์เอาต์ 2 มิติที่อยู่ใน 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)             )         }     } }

ประเด็นสำคัญเกี่ยวกับโค้ด

  • เมื่อประกาศ Orbiter นอกเลย์เอาต์ 2 มิติ Orbiter จะยึดกับ เอนทิตีระดับบนสุดที่ใกล้ที่สุด ในกรณีนี้ ออบิเตอร์จะยึดที่ด้านบนของSpatialRowที่ประกาศไว้
  • เลย์เอาต์เชิงพื้นที่ เช่น SpatialRow, SpatialColumn, SpatialBox มีเอนทิตีที่ไม่มีเนื้อหาเชื่อมโยงอยู่ ดังนั้น ออบิเตอร์ที่ประกาศใน เลย์เอาต์เชิงพื้นที่จะยึดกับเลย์เอาต์นั้น

ดูเพิ่มเติม