พัฒนา 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 ไม่ได้

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

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

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

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

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

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

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

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

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

@Composable
fun SpatialPanelContent() {
    Box(
        Modifier
            .background(color = Color.Black)
            .height(500.dp)
            .width(500.dp),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = "Spatial Panel",
            color = Color.White,
            fontSize = 25.sp
        )
    }
}

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

  • เนื่องจาก API ของ SpatialPanel เป็นแบบ Subspace Composables คุณจึงต้องเรียกใช้ API เหล่านี้ภายใน Subspace การเรียกใช้ฟังก์ชันเหล่านี้นอก Subspace จะทำให้เกิดข้อยกเว้น
  • ระบบได้ตั้งค่าขนาดของ SpatialPanel โดยใช้ข้อกำหนด height และ width ใน SubspaceModifier การละเว้นข้อกำหนดเหล่านี้จะทำให้ ระบบกำหนดขนาดของแผงตามการวัดเนื้อหาของแผง
  • อนุญาตให้ผู้ใช้ปรับขนาดหรือย้ายแผงโดยการเพิ่มตัวแก้ไข movable หรือ resizable
  • ดูรายละเอียดเกี่ยวกับการปรับขนาดและการวางตำแหน่งได้ในคำแนะนำการออกแบบแผงเชิงพื้นที่ ดูรายละเอียดเพิ่มเติมเกี่ยวกับการติดตั้งโค้ดได้ในเอกสารประกอบอ้างอิง

วิธีการทำงานของตัวแก้ไขพื้นที่ย่อยที่เคลื่อนย้ายได้

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

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

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

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

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

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

@Composable
fun OrbiterExample() {
    Orbiter(
        position = ContentEdge.Bottom,
        offset = 96.dp,
        alignment = Alignment.CenterHorizontally
    ) {
        Surface(Modifier.clip(CircleShape)) {
            Row(
                Modifier
                    .background(color = Color.Black)
                    .height(100.dp)
                    .width(600.dp),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = "Orbiter",
                    color = Color.White,
                    fontSize = 50.sp
                )
            }
        }
    }
}

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

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

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

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

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

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

@OptIn(ExperimentalSubspaceVolumeApi::class)
@Composable
fun ObjectInAVolume(show3DObject: Boolean) {

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

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

SpatialExternalSurface คือ 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 นี้แสดงผลอยู่หน้าแผงอื่นๆ ไม่ได้ ดังนั้นคุณจึงไม่ควรใช้ ตัวแก้ไขที่เคลื่อนย้ายได้หากมีแผงอื่นๆ ในเลย์เอาต์

เพิ่มแพลตฟอร์มสำหรับเนื้อหาวิดีโอที่ได้รับการคุ้มครองโดย 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

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

คุณยึด 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 มีเอนทิตีที่ไม่มีเนื้อหาเชื่อมโยงอยู่ ดังนั้น ออบิเตอร์ที่ประกาศใน เลย์เอาต์เชิงพื้นที่จะยึดกับเลย์เอาต์นั้น

ดูเพิ่มเติม