รองรับหน้าจอขนาดต่างๆ

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

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

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

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

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

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

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

แผนภาพที่แสดงรูปแบบของอุปกรณ์ต่างๆ ซึ่งรวมถึงโทรศัพท์ อุปกรณ์แบบพับได้ แท็บเล็ต และแล็ปท็อป
รูปที่ 1 รูปแบบของอุปกรณ์ ได้แก่ โทรศัพท์ อุปกรณ์แบบพับได้ แท็บเล็ต และแล็ปท็อป

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

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

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

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

@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

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

คอมโพสิเบิลที่ฝังไว้อย่างยืดหยุ่นสามารถนำมาใช้ซ้ำได้

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

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

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

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

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

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

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

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

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

ดังนั้น เราควรใช้ความกว้างที่คอมโพสิเบิลได้รับจริงเพื่อแสดงผล เรามี 2 วิธีในการรับค่าความกว้างนั้น

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

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

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

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

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

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

@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 Boolean flag ขึ้นเพื่อให้ระบบรักษาสถานะของผู้ใช้ไว้เมื่อการปรับขนาดทําให้เลย์เอาต์สลับระหว่างการซ่อนและแสดงคําอธิบาย ดังนี้

@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)
            }
        }
    }
}

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

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

ตัวอย่างแอป

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

วิดีโอ