เมื่อใช้ Jetpack Compose สำหรับ XR คุณจะสร้าง UI และเลย์เอาต์เชิงพื้นที่แบบประกาศได้โดยใช้แนวคิด Compose ที่คุ้นเคย เช่น แถวและคอลัมน์ ซึ่งจะช่วยให้คุณขยาย UI ของ Android ที่มีอยู่ไปยังพื้นที่ 3 มิติ หรือสร้างแอปพลิเคชัน 3 มิติที่สมจริงแบบใหม่ทั้งหมด
หากกำลังทำให้แอปที่มีอยู่ซึ่งอิงตาม Android Views เป็นโมเดลเชิงพื้นที่ คุณจะมีตัวเลือกการพัฒนาหลายอย่าง คุณสามารถใช้ API การทํางานร่วมกัน ใช้ Compose และ Views ร่วมกัน หรือทํางานกับไลบรารี SceneCore โดยตรงก็ได้ ดูรายละเอียดเพิ่มเติมได้ในคำแนะนำเกี่ยวกับวิธีทำงานกับมุมมอง
เกี่ยวกับพื้นที่ย่อยและคอมโพเนนต์ที่แยกแยะเสียงตามตำแหน่ง
เมื่อเขียนแอปสำหรับ Android XR คุณควรทำความเข้าใจแนวคิดของพื้นที่ย่อยและคอมโพเนนต์ที่ปรับเปลี่ยนพื้นที่
เกี่ยวกับพาร์ทเนอร์ย่อย
เมื่อพัฒนาแอปสำหรับ Android XR คุณจะต้องเพิ่มพื้นที่ย่อยลงในแอปหรือเลย์เอาต์ พื้นที่ย่อยคือการแบ่งพื้นที่ 3 มิติภายในแอป ซึ่งคุณสามารถวางเนื้อหา 3 มิติ สร้างเลย์เอาต์ 3 มิติ และเพิ่มมิติให้กับเนื้อหา 2 มิติ ระบบจะแสดงผลพื้นที่ย่อยก็ต่อเมื่อเปิดใช้การจัดวางเสียงเท่านั้น ในโฮมสเปซหรือในอุปกรณ์ที่ไม่ใช่ XR ระบบจะไม่สนใจโค้ดภายในพื้นที่ย่อยนั้น
การสร้างพื้นที่ทำงานย่อยทำได้ 2 วิธีดังนี้
setSubspaceContent()
: ฟังก์ชันนี้จะสร้าง พื้นที่ทำงานระดับแอป ซึ่งสามารถเรียกใช้ในกิจกรรมหลักได้เช่นเดียวกับที่ใช้setContent()
พื้นที่ทำงานย่อยระดับแอปมีความสูง กว้าง และลึกได้ไม่จำกัด ซึ่งโดยพื้นฐานแล้วจะเป็นผืนผ้าใบที่ไม่มีที่สิ้นสุดสำหรับเนื้อหาเชิงพื้นที่Subspace
: คอมโพสิชันนี้สามารถวางไว้ที่ใดก็ได้ภายในลําดับชั้น UI ของแอป ซึ่งช่วยให้คุณคงเลย์เอาต์สําหรับ UI 2 มิติและ UI เชิงพื้นที่ไว้ได้โดยไม่สูญเสียบริบทระหว่างไฟล์ ซึ่งจะช่วยให้แชร์สิ่งต่างๆ ได้ง่ายๆ เช่น สถาปัตยกรรมแอปที่มีอยู่ระหว่าง XR กับรูปแบบอุปกรณ์อื่นๆ โดยไม่ต้องยกสถานะผ่านทั้งต้นไม้ UI หรือออกแบบแอปใหม่
ดูข้อมูลเพิ่มเติมได้ที่เพิ่มพื้นที่ทำงานย่อยลงในแอป
เกี่ยวกับคอมโพเนนต์ที่วางตำแหน่งตามพื้นที่
คอมโพเนนต์ที่คอมโพสิเบิลในซับสเปซ: คอมโพเนนต์เหล่านี้จะแสดงผลได้ในซับสเปซเท่านั้น
โดยต้องใส่ไว้ใน Subspace
หรือ setSubspaceContent
ก่อนวางไว้ในเลย์เอาต์ 2 มิติ SubspaceModifier
ช่วยให้คุณเพิ่มแอตทริบิวต์ต่างๆ เช่น ระยะความลึก การเลื่อน และการวางตำแหน่ง ลงในคอมโพสิชันย่อยได้
คอมโพเนนต์อื่นๆ ที่ปรับเปลี่ยนให้เข้ากับพื้นที่ไม่จำเป็นต้องเรียกใช้ภายในซับสเปซ โดยประกอบด้วยองค์ประกอบ 2 มิติแบบดั้งเดิมที่รวมอยู่ในคอนเทนเนอร์เชิงพื้นที่ องค์ประกอบเหล่านี้ใช้ได้ภายในเลย์เอาต์ 2 มิติหรือ 3 มิติ หากกำหนดไว้สำหรับทั้ง 2 รูปแบบ เมื่อไม่ได้เปิดใช้การจัดวางเชิงพื้นที่ ระบบจะไม่สนใจฟีเจอร์ที่จัดวางเชิงพื้นที่และฟีเจอร์ดังกล่าวจะเปลี่ยนกลับไปเป็นฟีเจอร์ 2 มิติ
สร้างแผงเชิงพื้นที่
SpatialPanel
คือองค์ประกอบย่อยของพื้นที่ทำงานซึ่งช่วยให้คุณแสดงเนื้อหาแอปได้ เช่น คุณอาจแสดงการเล่นวิดีโอ ภาพนิ่ง หรือเนื้อหาอื่นๆ ในแผงพื้นที่ทำงาน
คุณสามารถใช้ 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 ) } }
ประเด็นสำคัญเกี่ยวกับรหัส
- เนื่องจาก
SpatialPanel
API เป็นแบบคอมโพสิเบิลของซับสเปซ คุณจึงต้องเรียกใช้ภายในSubspace
หรือsetSubspaceContent
การเรียกใช้นอกอวกาศย่อยจะทำให้เกิดข้อยกเว้น - อนุญาตให้ผู้ใช้ปรับขนาดหรือย้ายแผงโดยเพิ่มตัวแก้ไข
movable
หรือresizable
- ดูรายละเอียดเกี่ยวกับขนาดและตำแหน่งได้ในคำแนะนำการออกแบบแผงพื้นที่ทำงาน ดูรายละเอียดเพิ่มเติมเกี่ยวกับการติดตั้งใช้งานโค้ดได้ในเอกสารอ้างอิง
วิธีการทํางานของโมดิฟายเออร์พื้นที่ย่อยที่เคลื่อนย้ายได้
เมื่อผู้ใช้เลื่อนแผงออกไป เครื่องมือแก้ไขพื้นที่ทำงานย่อยแบบเคลื่อนย้ายได้จะปรับขนาดแผงโดยค่าเริ่มต้นในลักษณะที่คล้ายกับที่ระบบปรับขนาดแผงในพื้นที่ทำงานหลัก เนื้อหาสำหรับเด็กทั้งหมดจะรับค่าลักษณะการทำงานนี้ หากต้องการปิดใช้ ให้ตั้งค่าพารามิเตอร์ scaleWithDistance
เป็น false
สร้างดาวเทียมโคจร
วงโคจรคือคอมโพเนนต์ UI เชิงพื้นที่ ออกแบบมาเพื่อแนบกับแผงเชิงพื้นที่ เลย์เอาต์ หรือเอนทิตีอื่นๆ ที่เกี่ยวข้อง โดยปกติแล้ว องค์ประกอบที่โคจรจะมีรายการการนำทางและการดำเนินการตามบริบทที่เกี่ยวข้องกับเอนทิตีที่ยึดไว้ เช่น หากสร้างแผงเชิงพื้นที่เพื่อแสดงเนื้อหาวิดีโอ คุณสามารถเพิ่มตัวควบคุมการเล่นวิดีโอไว้ในภาพโคจร
ดังที่แสดงในตัวอย่างต่อไปนี้ ให้เรียกใช้ออบบิทภายในเลย์เอาต์ 2 มิติใน SpatialPanel
เพื่อรวมตัวควบคุมของผู้ใช้ เช่น การนําทาง ซึ่งจะดึงข้อมูลเหล่านั้นออกจากเลย์เอาต์ 2 มิติและแนบไปกับแผงเชิงพื้นที่ตามการกำหนดค่าของคุณ
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp) .movable() .resizable() ) { SpatialPanelContent() OrbiterExample() } }
@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 ) } } } }
ประเด็นสำคัญเกี่ยวกับรหัส
- เนื่องจาก Orbiter เป็นคอมโพเนนต์ UI เชิงพื้นที่ คุณจึงนําโค้ดไปใช้ซ้ำในเลย์เอาต์ 2 มิติหรือ 3 มิติได้ ในเลย์เอาต์ 2 มิติ แอปจะแสดงผลเฉพาะเนื้อหาภายในวงโคจรและจะไม่สนใจวงโคจรเอง
- ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้และออกแบบดาวเทียมโคจรได้ที่คำแนะนำด้านการออกแบบ
เพิ่มแผงพื้นที่หลายรายการลงในเลย์เอาต์พื้นที่
คุณสามารถสร้างแผงพื้นที่หลายรายการและวางไว้ในเลย์เอาต์พื้นที่ได้โดยใช้ 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
เพื่อให้แผงล้อมรอบผู้ใช้ ดูรายละเอียดได้ในคำแนะนำด้านการออกแบบ
ใช้ปริมาตรเพื่อวางวัตถุ 3 มิติในเลย์เอาต์
หากต้องการวางวัตถุ 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, ) } } }
@Composable fun ObjectInAVolume(show3DObject: Boolean) {
ข้อมูลเพิ่มเติม
- ดูหัวข้อเพิ่มโมเดล 3 มิติลงในแอปเพื่อให้เข้าใจวิธีโหลดเนื้อหา 3 มิติภายในวอลุ่มได้ดียิ่งขึ้น
เพิ่มแพลตฟอร์มสำหรับเนื้อหารูปภาพหรือวิดีโอ
SpatialExternalSurface
คือคอมโพสิชันย่อยที่สร้างและจัดการ Surface
ที่แอปสามารถวาดเนื้อหา เช่น รูปภาพหรือวิดีโอ SpatialExternalSurface
รองรับเนื้อหาแบบสเตอริโอสโคปหรือโมโนสโคป
ตัวอย่างนี้แสดงวิธีโหลดวิดีโอสเตอริโอแบบแสดงคู่กันโดยใช้ Media3 Exoplayer และ SpatialExternalSurface
@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
: รูปภาพหรือเฟรมวิดีโอมีคู่รูปภาพหรือเฟรมวิดีโอที่วางซ้อนกันแนวตั้ง โดยรูปภาพหรือเฟรมด้านบนแสดงมุมมองจากดวงตาซ้าย และรูปภาพหรือเฟรมด้านล่างแสดงมุมมองจากดวงตาขวา
SpatialExternalSurface
รองรับเฉพาะพื้นผิวสี่เหลี่ยมผืนผ้าเท่านั้นSurface
นี้จะไม่บันทึกเหตุการณ์การป้อนข้อมูล- คุณไม่สามารถซิงค์การเปลี่ยนแปลง
StereoMode
กับการเรนเดอร์แอปพลิเคชันหรือการถอดรหัสวิดีโอ - คอมโพสิชันนี้ไม่สามารถแสดงผลอยู่หน้าแผงอื่นๆ คุณจึงไม่ควรใช้ตัวแก้ไขที่เคลื่อนย้ายได้หากมีแผงอื่นๆ ในเลย์เอาต์
เพิ่มคอมโพเนนต์ UI เชิงพื้นที่อื่นๆ
คอมโพเนนต์ UI เชิงพื้นที่สามารถวางไว้ที่ใดก็ได้ในลําดับชั้น UI ของแอปพลิเคชัน คุณนําองค์ประกอบเหล่านี้มาใช้ซ้ำใน UI 2 มิติได้ และแอตทริบิวต์เชิงพื้นที่จะปรากฏขึ้นก็ต่อเมื่อเปิดใช้ความสามารถเชิงพื้นที่เท่านั้น ซึ่งจะช่วยให้คุณเพิ่มระดับให้กับเมนู กล่องโต้ตอบ และคอมโพเนนต์อื่นๆ ได้โดยไม่ต้องเขียนโค้ดซ้ำ ดูตัวอย่าง UI เชิงพื้นที่ต่อไปนี้เพื่อให้เข้าใจวิธีใช้องค์ประกอบเหล่านี้ได้ดียิ่งขึ้น
คอมโพเนนต์ UI |
เมื่อเปิดใช้การจัดวางเสียงตามตำแหน่ง |
ในสภาพแวดล้อม 2 มิติ |
---|---|---|
|
แผงจะดันกลับเล็กน้อยใน Z-depth เพื่อแสดงกล่องโต้ตอบที่ยกระดับ |
กลับไปเป็น 2 มิติ |
|
แผงจะดันกลับเล็กน้อยใน Z-depth เพื่อแสดงป๊อปอัปที่ยกสูงขึ้น |
เปลี่ยนกลับไปเป็น 2 มิติ |
|
|
รายการที่ไม่มีระดับความสูงเชิงพื้นที่ |
SpatialDialog
นี่เป็นตัวอย่างของกล่องโต้ตอบที่เปิดขึ้นหลังจากรอสักครู่ เมื่อใช้ SpatialDialog
กล่องโต้ตอบจะปรากฏที่ระดับความลึก z เดียวกับแผงเชิงพื้นที่ และระบบจะดันแผงกลับ 125dp เมื่อเปิดใช้การจัดวางเชิงพื้นที่ นอกจากนี้ คุณยังใช้ SpatialDialog
ได้เมื่อไม่ได้เปิดใช้การจัดวางเสียงแบบ 3 มิติ ซึ่งในกรณีนี้ 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 ไม่รองรับ คุณสามารถทำงานกับ PanelEntities
และกราฟฉากได้โดยตรงโดยใช้ SceneCore
API
ยึดตำแหน่งของภาพโคจรตามเลย์เอาต์เชิงพื้นที่และเอนทิตีโฟลเดอร์อื่นๆ
คุณสามารถยึดออบเจ็กต์ที่โคจรไว้กับเอนทิตีใดก็ได้ที่ประกาศไว้ในคอมโพสิท ซึ่งเกี่ยวข้องกับการประกาศออบบิเทอร์ในเลย์เอาต์เชิงพื้นที่ขององค์ประกอบ UI เช่น SpatialRow
, SpatialColumn
หรือ SpatialBox
วัตถุโคจรจะยึดอยู่กับเอนทิตีหลักซึ่งอยู่ใกล้กับตำแหน่งที่คุณประกาศมากที่สุด
ลักษณะการทํางานของวงโคจรจะกําหนดโดยตําแหน่งที่คุณประกาศดังนี้
- ในเลย์เอาต์ 2 มิติที่รวมอยู่ใน
SpatialPanel
(ดังที่แสดงในข้อมูลโค้ด preceding code) วงโคจรจะยึดกับSpatialPanel
นั้น - ใน
Subspace
วงโคจรจะยึดกับเอนทิตีหลักที่ใกล้ที่สุด ซึ่งเป็นเลย์เอาต์เชิงพื้นที่ที่ประกาศวงโคจร
ตัวอย่างต่อไปนี้แสดงวิธียึดดาวเทียมโคจรกับแถวเชิงพื้นที่
Subspace { SpatialRow { Orbiter( position = OrbiterEdge.Top, offset = EdgeOffset.inner(8.dp), shape = SpatialRoundedCornerShape(size = CornerSize(50)) ) { Text( "Hello World!", style = MaterialTheme.typography.h2, 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) ) } } }
ประเด็นสำคัญเกี่ยวกับรหัส
- เมื่อคุณประกาศออบบิทเตอร์นอกเลย์เอาต์ 2 มิติ ออบบิทเตอร์จะยึดกับเอนทิตีหลักที่อยู่ใกล้ที่สุด ในกรณีนี้ วงโคจรจะยึดอยู่ที่ด้านบนของ
SpatialRow
ที่ประกาศ - เลย์เอาต์เชิงพื้นที่ เช่น
SpatialRow
,SpatialColumn
,SpatialBox
ทั้งหมดมีเอนทิตีที่ไม่มีเนื้อหาเชื่อมโยงอยู่ ดังนั้น ดาวเทียมที่ประกาศในเลย์เอาต์เชิงพื้นที่จะยึดตามเลย์เอาต์นั้น
ดูเพิ่มเติม
- เพิ่มโมเดล 3 มิติลงในแอป
- พัฒนา UI สําหรับแอปที่ทำงานบน Android Views
- ใช้การออกแบบ Material Design สำหรับ XR