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 คือคอมโพสได้ของพื้นที่ย่อยที่ช่วยให้คุณแสดงเนื้อหาของแอป
ได้ เช่น คุณสามารถแสดงการเล่นวิดีโอ ภาพนิ่ง หรือเนื้อหา
อื่นๆ ในแผงเชิงพื้นที่

คุณสามารถใช้ 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 - อนุญาตให้ผู้ใช้ปรับขนาดแผงโดยเพิ่มตัวปรับแต่งพื้นที่ย่อย
resizablesubspace - ดูรายละเอียดเกี่ยวกับการปรับขนาดและ การวางตำแหน่งได้ในคำแนะนำการออกแบบแผงเชิงพื้นที่ ดูข้อมูลจำเพาะเพิ่มเติมเกี่ยวกับการใช้งานโค้ดได้ในเอกสารอ้างอิงของเรา
วิธีทำงานของตัวปรับแต่ง 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
ข้อมูลเพิ่มเติม
- ดูหัวข้อเพิ่มโมเดล 3 มิติลงในแอปเพื่อทำความเข้าใจวิธีโหลดเนื้อหา 3 มิติ
ภายใน
SceneCoreEntityให้ดียิ่งขึ้น
เพิ่มพื้นผิวสำหรับเนื้อหารูปภาพหรือวิดีโอ
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 มิติ |
|---|---|---|
|
แผงจะเลื่อนถอยหลังเล็กน้อยในความลึกแกน Z เพื่อแสดงกล่องโต้ตอบที่ยกระดับ |
กลับไปใช้ 2 มิติ |
|
แผงจะเลื่อนถอยหลังเล็กน้อยในความลึกแกน Z เพื่อแสดงป๊อปอัปที่ยกระดับ |
กลับไปใช้ 2 มิติ |
|
คุณสามารถตั้งค่า |
แสดงโดยไม่มีระดับความสูงเชิงพื้นที่ |
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นั้น - ใน
SubspaceOrbiter จะยึดกับเอนทิตีหลักที่ใกล้ที่สุด ซึ่งก็คือเลย์เอาต์เชิงพื้นที่ที่ประกาศ 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 ที่ประกาศไว้ในเลย์เอาต์เชิงพื้นที่จึงยึดกับเลย์เอาต์นั้น