เกี่ยวกับ WindowInsetsRulers

WindowInsets เป็น API มาตรฐานใน Jetpack Compose สำหรับจัดการพื้นที่ของหน้าจอที่ถูกบดบังบางส่วนหรือทั้งหมดโดย UI ของระบบ พื้นที่เหล่านี้รวมถึงแถบสถานะ แถบนำทาง และแป้นพิมพ์บนหน้าจอ หรือจะส่ง WindowInsetsRulers ที่กำหนดไว้ล่วงหน้า เช่น SafeDrawing ไปยัง Modifier.fitInside หรือ Modifier.fitOutside เพื่อจัดแนวเนื้อหาให้สอดคล้องกับแถบระบบและหน้าจอรอยบาก หรือสร้าง WindowInsetsRulers ที่กำหนดเองก็ได้

ข้อดีของ WindowInsetsRulers

  • หลีกเลี่ยงความซับซ้อนในการใช้ทรัพยากร: ฟังก์ชันนี้จะทำงานในระยะการวางตำแหน่งของเลย์เอาต์ ซึ่งหมายความว่าระบบจะข้ามเชนการใช้ส่วนแทรกโดยสมบูรณ์ และสามารถระบุตำแหน่งที่ถูกต้องและแน่นอนของแถบระบบและหน้าจอรอยบากได้เสมอ ไม่ว่าเลย์เอาต์หลักจะทำอะไรก็ตาม การใช้เมธอด Modifier.fitInside หรือ Modifier.fitOutside จะช่วยแก้ปัญหาเมื่อ Composable ระดับบนใช้ส่วนแทรกอย่างไม่ถูกต้อง
  • หลีกเลี่ยงแถบระบบได้อย่างง่ายดาย: ช่วยให้เนื้อหาแอปหลีกเลี่ยงแถบระบบและหน้าจอรอยบากได้ และอาจตรงไปตรงมากว่าการใช้ WindowInsets โดยตรง
  • ปรับแต่งได้สูง: นักพัฒนาแอปสามารถจัดแนวเนื้อหาให้ตรงกับไม้บรรทัดที่กำหนดเอง และควบคุมเลย์เอาต์ได้อย่างแม่นยำด้วยเลย์เอาต์ที่กำหนดเอง

ข้อเสียของ WindowInsetsRulers

  • ใช้สำหรับการวัดผลไม่ได้: เนื่องจากทำงานในระยะการวางตำแหน่ง ข้อมูลตำแหน่งที่ให้จึงไม่พร้อมใช้งานในระยะการวัดผลก่อนหน้า

จัดแนวเนื้อหาด้วยเมธอด Modifier

Modifier.fitInside ช่วยให้แอปจัดเนื้อหาให้สอดคล้องกับแถบระบบและหน้าจอรอยบากได้ ซึ่งใช้แทน WindowInsets ได้ Modifier.fitOutside มักจะตรงกันข้ามกับ Modifier.fitInside

เช่น หากต้องการยืนยันว่าเนื้อหาแอปหลีกเลี่ยงแถบระบบและหน้าจอรอยบาก คุณสามารถใช้ fitInside(WindowInsetsRulers.safeDrawing.current)

@Composable
fun FitInsideDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc...
            .fitInside(WindowInsetsRulers.SafeDrawing.current)
    )
}

ตารางต่อไปนี้แสดงลักษณะของเนื้อหาแอปที่มีไม้บรรทัดที่กำหนดไว้ล่วงหน้าซึ่งมี Modifier.fitInside หรือ Modifier.fitOutside

ประเภทไม้บรรทัดที่กำหนดไว้ล่วงหน้า

Modifier.fitInside

Modifier.fitOutside

DisplayCutout

Ime

ไม่มี

NavigationBars

SafeDrawing

ไม่มี (ใช้ StatusBar, CaptionBar, NavigationBar แทน)

StatusBar

การใช้ Modifier.fitInside และ Modifier.fitOutside กำหนดให้ Composable ต้องถูกจำกัด ซึ่งหมายความว่าคุณต้องกำหนดตัวแก้ไข เช่น Modifier.size หรือ Modifier.fillMaxSize

ไม้บรรทัดบางอัน เช่น Modifier.fitOutside ใน SafeDrawing และ SystemBars จะแสดงผลไม้บรรทัดหลายอัน ในกรณีนี้ Android จะวาง Composable โดยใช้ไม้บรรทัด 1 อันจากซ้าย บน ขวา ล่าง

หลีกเลี่ยง IME ด้วย Modifier.fitInside

หากต้องการจัดการองค์ประกอบด้านล่างด้วย IME ที่มี Modifier.fitInside ให้ส่ง RectRuler ที่ใช้ค่าด้านในสุดของ NavigationBar และ Ime

@Composable
fun FitInsideWithImeDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.NavigationBars.current,
                    WindowInsetsRulers.Ime.current
                )
            )
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier.align(Alignment.BottomStart).fillMaxWidth()
        )
    }
}

หลีกเลี่ยงแถบสถานะและแถบคำบรรยายด้วย Modifier.fitInside

ในทำนองเดียวกัน หากต้องการยืนยันว่าองค์ประกอบด้านบนหลีกเลี่ยงแถบสถานะและแถบคำอธิบายภาพร่วมกับ Modifier.fitInside ให้ส่ง RectRuler ที่ใช้ค่าด้านในสุดของ StatusBars และ CaptionBar

@Composable
fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.StatusBars.current,
                    WindowInsetsRulers.CaptionBar.current
                )
            )
    )
}

สร้าง WindowInsetsRulers ที่กำหนดเอง

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

@Composable
fun WindowInsetsRulersDemo(modifier: Modifier) {
    Box(
        contentAlignment = BottomCenter,
        modifier = modifier
            .fillMaxSize()
            // The mistake that causes issues downstream, as .padding doesn't consume insets.
            // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars),
            // assume it's difficult to identify this issue to see how WindowInsetsRulers can help.
            .padding(WindowInsets.navigationBars.asPaddingValues())
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier
                // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child
                // Composable without having to fix the parent upstream.
                .alignToSafeDrawing()

            // .imePadding()
            // .fillMaxWidth()
        )
    }
}

fun Modifier.alignToSafeDrawing(): Modifier {
    return layout { measurable, constraints ->
        if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
            val placeable = measurable.measure(constraints)
            val width = placeable.width
            val height = placeable.height
            layout(width, height) {
                val bottom = WindowInsetsRulers.SafeDrawing.current.bottom
                    .current(0f).roundToInt() - height
                val right = WindowInsetsRulers.SafeDrawing.current.right
                    .current(0f).roundToInt()
                val left = WindowInsetsRulers.SafeDrawing.current.left
                    .current(0f).roundToInt()
                measurable.measure(Constraints.fixed(right - left, height))
                    .place(left, bottom)
            }
        } else {
            val placeable = measurable.measure(constraints)
            layout(placeable.width, placeable.height) {
                placeable.place(0, 0)
            }
        }
    }
}

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

ยืนยันว่ามีการจำกัดระดับบน

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

ในทำนองเดียวกัน การวาง Composable ที่ใช้ WindowInsetsRulers ไว้ในคอนเทนเนอร์แบบเลื่อนได้ เช่น verticalScroll อาจทำให้เกิดลักษณะการทำงานที่ไม่คาดคิด เนื่องจากคอนเทนเนอร์แบบเลื่อนได้มีข้อจำกัดด้านความสูงที่ไม่มีขอบเขต ซึ่งไม่เข้ากันกับตรรกะของไม้บรรทัด