รองรับขนาดการแสดงผลที่ต่างกัน

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

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

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

ในฐานะชุดเครื่องมือ UI แบบประกาศ Jetpack Compose เหมาะอย่างยิ่งสำหรับการออกแบบและใช้เลย์เอาต์ที่เปลี่ยนแปลงแบบไดนามิกเพื่อแสดงผลเนื้อหาที่แตกต่างกันในขนาดการแสดงผลที่แตกต่างกัน

ทำให้การเปลี่ยนแปลงเลย์เอาต์ขนาดใหญ่สำหรับ Composable ระดับเนื้อหามีความชัดเจน

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

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

รูปที่ 1 รูปแบบของอุปกรณ์โทรศัพท์ อุปกรณ์แบบพับได้ แท็บเล็ต และแล็ปท็อป

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

แต่ให้ตัดสินใจโดยอิงตามส่วนจริงของหน้าจอที่จัดสรรให้กับแอปของคุณตามที่อธิบายไว้ในเมตริกหน้าต่างปัจจุบันที่ได้จากไลบรารี WindowManager ของ Jetpack ดูตัวอย่างวิธีใช้ WindowManager ในแอป Compose ได้ที่ตัวอย่าง JetNews

การทำให้เลย์เอาต์ปรับเปลี่ยนตามพื้นที่แสดงผลที่พร้อมใช้งานยังช่วยลดปริมาณการจัดการพิเศษที่จำเป็นต่อการรองรับแพลตฟอร์มต่างๆ เช่น ChromeOS และรูปแบบต่างๆ เช่น แท็บเล็ตและอุปกรณ์แบบพับได้

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

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

@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass
) {
    // Decide whether to show the top app bar based on window size class.
    val showTopAppBar = windowSizeClass.isHeightAtLeastBreakpoint(WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND)

    // MyScreen logic is based on the showTopAppBar boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

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

Composable ที่ฝังแบบยืดหยุ่นสามารถนำมาใช้ซ้ำได้

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

ลองนึกภาพ Composable ที่ฝังอยู่ซึ่งใช้เลย์เอาต์แบบรายละเอียดรายการ ซึ่งอาจแสดงแผงเดียวหรือ 2 แผงข้างกัน

แอปที่แสดงแผง 2 แผงเคียงข้างกัน
รูปที่ 2 แอปที่แสดงเลย์เอาต์รายละเอียดรายการทั่วไป โดย 1 คือพื้นที่รายการ และ 2 คือพื้นที่รายละเอียด

การตัดสินใจเกี่ยวกับรายละเอียดรายการควรเป็นส่วนหนึ่งของเลย์เอาต์โดยรวมของแอป ดังนั้นการตัดสินใจจึงส่งต่อจาก Composable ระดับเนื้อหา

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

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

รูปที่ 3 การ์ดแคบที่แสดงเฉพาะไอคอนและชื่อ และการ์ดกว้างที่แสดงไอคอน ชื่อ และคำอธิบายสั้นๆ

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

เนื่องจาก Composable ไม่ใช่ Composable ระดับเนื้อหา จึงไม่ควรใช้เมตริกหน้าต่างปัจจุบันโดยตรง

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

ใช้ความกว้างที่ Composable ได้รับจริงเพื่อแสดงผลตัวเอง คุณมี 2 ตัวเลือกในการกำหนดความกว้างดังกล่าว ดังนี้

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

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

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

ทำให้ข้อมูลทั้งหมดพร้อมใช้งานสำหรับการแสดงผลขนาดต่างๆ

เมื่อใช้ Composable ที่ใช้ประโยชน์จากพื้นที่แสดงผลเพิ่มเติม คุณอาจต้องการเพิ่มประสิทธิภาพและโหลดข้อมูลเป็นผลข้างเคียงของขนาดการแสดงผลปัจจุบัน

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

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

จากตัวอย่าง Card โปรดทราบว่าระบบจะส่ง description ไปยัง Card เสมอ แม้ว่าจะใช้ description เฉพาะเมื่อความกว้างอนุญาตให้แสดงเท่านั้น แต่ Card ต้องใช้ description เสมอ ไม่ว่าความกว้างที่ใช้ได้จะเป็นเท่าใดก็ตาม

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

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

ตัวอย่างเช่น คุณสามารถยกแฟล็กบูลีน showMore เพื่อให้ระบบคงสถานะแอปไว้เมื่อการปรับขนาดการแสดงผลทำให้เลย์เอาต์สลับไปมาระหว่างการซ่อนและการแสดงเนื้อหา

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับเลย์เอาต์แบบปรับอัตโนมัติใน Compose ได้ที่แหล่งข้อมูลต่อไปนี้

แอปตัวอย่าง

  • CanonicalLayouts เป็นที่เก็บข้อมูลของรูปแบบการออกแบบที่ได้รับการพิสูจน์แล้วว่ามอบประสบการณ์ของผู้ใช้ที่ดีที่สุดบนจอแสดงผลขนาดใหญ่
  • JetNews แสดงวิธีออกแบบแอปที่ปรับ UI ให้ใช้ประโยชน์จากพื้นที่แสดงผลที่มีอยู่
  • Reply เป็นตัวอย่างที่ปรับเปลี่ยนได้สำหรับการรองรับอุปกรณ์เคลื่อนที่ แท็บเล็ต และอุปกรณ์แบบพับได้
  • Now in Android เป็นแอปที่ใช้เลย์เอาต์แบบปรับเปลี่ยนได้เพื่อรองรับขนาดการแสดงผลต่างๆ

วิดีโอ