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

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

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

เกี่ยวกับพื้นที่ย่อยและคอมโพเนนต์ที่แยกแยะเสียงตามตำแหน่ง

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

เกี่ยวกับพาร์ทเนอร์ย่อย

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

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

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

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

เกี่ยวกับคอมโพเนนต์ที่วางตำแหน่งตามพื้นที่

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

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

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

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

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

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

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

// 2D content placed within the spatial panel
@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
  • ดูรายละเอียดเกี่ยวกับขนาดและตำแหน่งได้ในคำแนะนำการออกแบบแผงพื้นที่ทำงาน ดูรายละเอียดเพิ่มเติมเกี่ยวกับการติดตั้งใช้งานโค้ดได้ในเอกสารอ้างอิง

สร้างดาวเทียมโคจร

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

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

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

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

//2D content inside Orbiter
@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 มิติในเลย์เอาต์ คุณจะต้องคอมโพสิชันย่อยที่เรียกว่าปริมาตร ต่อไปนี้เป็นตัวอย่างวิธีดำเนินการ

ตัวอย่างวัตถุ 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) {
    val xrCoreSession = 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 Object here
                }
            }
        }
    }
}

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

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

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

คอมโพเนนต์ UI

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

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

SpatialDialog

แผงจะดันกลับเล็กน้อยใน Z-depth เพื่อแสดงกล่องโต้ตอบที่ยกระดับ

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

SpatialPopUp

แผงจะดันกลับเล็กน้อยใน Z-depth เพื่อแสดงป๊อปอัปที่ยกสูงขึ้น

เปลี่ยนกลับไปเป็น 2 มิติ PopUp

SpatialElevation

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

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

SpatialDialog

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

@Composable
fun DelayedDialog() {
   var showDialog by remember { mutableStateOf(false) }
   LaunchedEffect(Unit) {
       Handler(Looper.getMainLooper()).postDelayed({
           showDialog = true
       }, 3000)
   }
   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 ไม่รองรับ คุณสามารถทำงานกับ 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.titleLarge,
                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 ทั้งหมดมีเอนทิตีที่ไม่มีเนื้อหาเชื่อมโยงอยู่ ดังนั้น ดาวเทียมที่ประกาศในเลย์เอาต์เชิงพื้นที่จะยึดตามเลย์เอาต์นั้น

ดูเพิ่มเติม