พัฒนา UI เชิงพื้นที่ด้วย Jetpack Compose สำหรับ XR

อุปกรณ์ XR ที่รองรับ
คำแนะนำนี้จะช่วยคุณสร้างประสบการณ์การใช้งานสำหรับอุปกรณ์ XR ประเภทต่อไปนี้
ชุดหูฟัง XR
แว่นตา XR แบบมีสาย

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

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

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

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

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

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

การสร้างพื้นที่ย่อยทำได้หลายวิธีดังนี้

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

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

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

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

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

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

A 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 การเรียกใช้ภายนอกพื้นที่ย่อยจะทำให้เกิดข้อยกเว้น
  • ขนาดของ SpatialPanel ได้รับการตั้งค่าโดยใช้ข้อกำหนด height และ width ใน SubspaceModifier การละเว้นข้อกำหนดเหล่านี้จะทำให้ขนาดของแผงกำหนดโดยการวัดขนาดของเนื้อหา
  • อนุญาตให้ผู้ใช้ย้ายแผงโดยเพิ่มตัวปรับแต่งพื้นที่ย่อย movable
  • อนุญาตให้ผู้ใช้ปรับขนาดแผงโดยเพิ่มตัวปรับแต่งพื้นที่ย่อย resizable subspace
  • ดูรายละเอียดเกี่ยวกับการปรับขนาดและ การวางตำแหน่งได้ในคำแนะนำการออกแบบแผงเชิงพื้นที่ ดูข้อมูลจำเพาะเพิ่มเติมเกี่ยวกับการใช้งานโค้ดได้ในเอกสารอ้างอิงของเรา

วิธีทำงานของตัวปรับแต่ง movable

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

สร้าง Orbiter

Orbiter คือคอมโพเนนต์ UI เชิงพื้นที่ โดยได้รับการออกแบบมาให้แนบกับคอมโพเนนต์แผงเชิงพื้นที่หรือเลย์เอาต์เชิงพื้นที่ที่เกี่ยวข้อง เช่น SpatialColumn, SpatialRow, หรือ SpatialBox โดยปกติแล้ว Orbiter จะมีรายการการนำทางและการดำเนินการตามบริบทที่เกี่ยวข้องกับเอนทิตีที่ยึดไว้ ตัวอย่างเช่น หากคุณสร้างแผงเชิงพื้นที่เพื่อแสดงเนื้อหาวิดีโอ คุณสามารถเพิ่มตัวควบคุมการเล่นวิดีโอไว้ใน 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
                )
            }
        }
    }
}

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

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

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

คุณสามารถสร้างแผงเชิงพื้นที่หลายแผงและวางไว้ภายในเลย์เอาต์เชิงพื้นที่ โดยใช้ SpatialRow, SpatialColumn, SpatialBox และ SpatialSpacer

ตัวอย่างแผงเชิงพื้นที่หลายรายการในเลย์เอาต์เชิงพื้นที่

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีดำเนินการ

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

เพิ่มออบเจ็กต์ 3 มิติลงในเลย์เอาต์โดยใช้ SpatialGltfModel

Android XR รองรับรูปแบบ glTF สำหรับโมเดล 3 มิติ ซึ่งโดยปกติจะบันทึกเป็นไฟล์ .glb หากต้องการเพิ่มออบเจ็กต์เหล่านี้ลงในเลย์เอาต์ คุณควรใช้คอมโพสได้ SpatialGltfModel API นี้จะช่วยลดความซับซ้อนของกระบวนการโหลดชิ้นงานและการจัดการสถานะของชิ้นงาน

หากต้องการแสดงโมเดล ให้กำหนดแหล่งที่มาและสถานะของโมเดลก่อนโดยใช้ rememberSpatialGltfModelState คุณสามารถโหลด โมเดลจากโฟลเดอร์ assets ของแอป, URI หรือ raw data

val modelState = rememberSpatialGltfModelState(
    source = SpatialGltfModelSource.fromPath(
        Paths.get("models/model_name.glb")
    )
)

เมื่อกำหนดสถานะแล้ว ให้ใช้คอมโพสได้ SpatialGltfModel เพื่อแสดงผลภายในพื้นที่ย่อย

SpatialGltfModel(state = modelState, modifier = SubspaceModifier)

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

  • การโหลดแบบไม่พร้อมกัน: ระบบจะโหลดโมเดลแบบไม่พร้อมกัน ในระหว่างการคอมโพสครั้งแรก ขนาดโดยธรรมชาติของโมเดลอาจเป็น 0 เลย์เอาต์จะวัดขนาดอีกครั้งเมื่อโมเดลพร้อม
  • การควบคุมสถานะ: ใช้ SpatialGltfModelState.status เพื่อค้นหาสถานะการโหลด หรือเพื่อควบคุมแอนิเมชัน
  • การปรับขนาดและการปรับสเกล: โดยค่าเริ่มต้น ขนาดเลย์เอาต์จะตรงกับกล่องขอบเขตของชิ้นงาน คุณสามารถลบล้างค่านี้ด้วย SubspaceModifier.size เพื่อปรับสเกลโมเดลอย่างสม่ำเสมอให้พอดีกับขอบเขตที่ระบุ

ใช้ SceneCoreEntity เพื่อวางเอนทิตีในเลย์เอาต์

คอมโพสได้ SceneCoreEntity จะเชื่อมโยงไลบรารี Jetpack SceneCore และ Compose for XR เพื่อให้คุณใช้ เอนทิตีที่สร้างด้วย SceneCore ในเลย์เอาต์ Compose ได้ ซึ่งจะช่วยให้คุณสร้างเอนทิตีระดับล่างและคอมโพเนนต์ที่กำหนดเองได้ ขณะเดียวกันก็อนุญาตให้ Compose ปรับขนาด วางตำแหน่ง เปลี่ยนระดับบน เพิ่มองค์ประกอบย่อย และใช้ตัวปรับแต่งกับเอนทิตีเหล่านั้นได้

Subspace {
    SceneCoreEntity(
        modifier = SubspaceModifier.offset(x = 50.dp),
        factory = {
            SurfaceEntity.create(
                session = session,
                pose = Pose.Identity,
                stereoMode = SurfaceEntity.StereoMode.MONO
            )
        },
        update = { entity ->
            // compose state changes may be applied to the
            // SceneCore entity here.
            entity.stereoMode = SurfaceEntity.StereoMode.SIDE_BY_SIDE
        },
        sizeAdapter =
            SceneCoreEntitySizeAdapter({
                IntSize2d(it.width, it.height)
            }),
    ) {
        // Content here will be children of the SceneCoreEntity
        // in the scene graph.
    }
}

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

  • บล็อก Factory: บล็อก Factory คือตำแหน่งที่คุณเริ่มต้นเอนทิตี SceneCore ที่อยู่เบื้องหลัง
  • บล็อก Update: ใช้บล็อก Update เพื่อแก้ไขพร็อพเพอร์ตี้ของเอนทิตีเพื่อตอบสนองต่อการเปลี่ยนแปลงในสถานะ Compose
  • การปรับขนาด: sizeAdapter จะสื่อสารขนาดของเอนทิตีกลับไปยังระบบเลย์เอาต์ Compose

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

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

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

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

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

หากต้องการสร้างพื้นผิวที่ปลอดภัย ให้ตั้งค่าพารามิเตอร์ surfaceProtection เป็น SurfaceProtection.Protected ในคอมโพสได้ SpatialExternalSurface นอกจากนี้ คุณต้องกำหนดค่า Media3 Exoplayer ด้วยข้อมูล DRM ที่เหมาะสมเพื่อจัดการการขอรับใบอนุญาตจากเซิร์ฟเวอร์ใบอนุญาต

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

@OptIn(ExperimentalComposeApi::class)
@Composable
fun DrmSpatialVideoPlayer() {
    val context = LocalContext.current
    Subspace {
        SpatialExternalSurface(
            modifier = SubspaceModifier
                .width(1200.dp)
                .height(676.dp),
            stereoMode = StereoMode.SideBySide,
            surfaceProtection = SurfaceProtection.Protected
        ) {
            val exoPlayer = remember { ExoPlayer.Builder(context).build() }

            // Define the URI for your DRM-protected content and license server.
            val videoUri = "https://your-content-provider.com/video.mpd"
            val drmLicenseUrl = "https://your-license-server.com/license"

            // Build a MediaItem with the necessary DRM configuration.
            val mediaItem = MediaItem.Builder()
                .setUri(videoUri)
                .setDrmConfiguration(
                    MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
                        .setLicenseUri(drmLicenseUrl)
                        .build()
                )
                .build()

            onSurfaceCreated { surface ->
                // The created surface is secure and can be used by the player.
                exoPlayer.setVideoSurface(surface)
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.prepare()
                exoPlayer.play()
            }

            onSurfaceDestroyed { exoPlayer.release() }
        }
    }
}

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

  • พื้นผิวที่ได้รับการคุ้มครอง: การตั้งค่า surfaceProtection = SurfaceProtection.Protected บน SpatialExternalSurface เป็นสิ่งสำคัญเพื่อให้ Surfaceที่อยู่เบื้องหลังได้รับการสนับสนุนโดยบัฟเฟอร์ที่ปลอดภัยซึ่งเหมาะสำหรับเนื้อหา DRM
  • การกำหนดค่า DRM: คุณต้องกำหนดค่า MediaItem ด้วยรูปแบบ DRM (เช่น C.WIDEVINE_UUID) และ URI ของเซิร์ฟเวอร์ใบอนุญาต ExoPlayer จะใช้ข้อมูลนี้เพื่อจัดการเซสชัน DRM
  • เนื้อหาที่ปลอดภัย: เมื่อแสดงผลไปยังพื้นผิวที่ได้รับการคุ้มครอง ระบบจะถอดรหัสและแสดงเนื้อหาวิดีโอในเส้นทางที่ปลอดภัย ซึ่งช่วยให้เป็นไปตามข้อกำหนดการออกใบอนุญาตเนื้อหา นอกจากนี้ ยังป้องกันไม่ให้เนื้อหาปรากฏในการจับภาพหน้าจอ

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

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

คอมโพเนนต์ UI

เมื่อเปิดใช้การสร้างพื้นที่

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

SpatialDialog

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

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

SpatialPopup

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

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

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")
                }
            }
        }
    }
}

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

  • นี่คือตัวอย่าง SpatialDialog การใช้ SpatialPopup และ SpatialElevation มีลักษณะคล้ายกันมาก ดูรายละเอียดเพิ่มเติมได้ในเอกสารอ้างอิง API

สร้างแผงและเลย์เอาต์ที่กำหนดเอง

หากต้องการสร้างแผงที่กำหนดเองซึ่ง Compose for XR ไม่รองรับ คุณสามารถทำงาน กับอินสแตนซ์ PanelEntity และกราฟฉากได้โดยตรงโดยใช้ API ของ SceneCore

ยึด Orbiter กับแผงและเลย์เอาต์เชิงพื้นที่

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

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

  • ในเลย์เอาต์ 2 มิติที่ห่อหุ้มไว้ใน SpatialPanel (ดังที่แสดงใน ข้อมูลโค้ดก่อนหน้า) Orbiter จะยึดกับ SpatialPanel นั้น
  • ใน Subspace Orbiter จะยึดกับเอนทิตีหลักที่ใกล้ที่สุด ซึ่งก็คือเลย์เอาต์เชิงพื้นที่ที่ประกาศ Orbiter ไว้

ตัวอย่างต่อไปนี้แสดงวิธียึด Orbiter กับแถวเชิงพื้นที่

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

ดูเพิ่มเติม