สร้างแอปแบบปรับเปลี่ยนได้ด้วย Jetpack Compose

1. บทนำ

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

ก่อนจะเริ่มใช้งาน คุณควรทำความเข้าใจความหมายของความสามารถในการปรับตัว

ความสามารถในการปรับตัว

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

ดูข้อมูลเพิ่มเติมได้ที่การออกแบบแบบปรับอัตโนมัติ

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

สิ่งที่คุณจะได้เรียนรู้

  • วิธีออกแบบแอปให้กำหนดเป้าหมายขนาดหน้าต่างทั้งหมดด้วย Jetpack Compose
  • วิธีกำหนดเป้าหมายแอปสำหรับอุปกรณ์พับได้รุ่นต่างๆ
  • วิธีใช้การนำทางประเภทต่างๆ เพื่อการเข้าถึงและการช่วยเหลือพิเศษที่ดีขึ้น
  • วิธีใช้คอมโพเนนต์ Material 3 เพื่อมอบประสบการณ์ที่ดีที่สุดสำหรับขนาดหน้าต่างทุกขนาด

สิ่งที่คุณต้องมี

  • Android Studio เวอร์ชันเสถียรล่าสุด
  • อุปกรณ์เสมือนที่ปรับขนาดได้ของ Android 13
  • มีความรู้เกี่ยวกับ Kotlin
  • ความเข้าใจพื้นฐานเกี่ยวกับ Compose (เช่น คำอธิบายประกอบ @Composable)
  • มีความคุ้นเคยพื้นฐานกับเลย์เอาต์ Compose (เช่น Row และ Column)
  • คุ้นเคยกับตัวแก้ไขในระดับพื้นฐาน (เช่น Modifier.padding())

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

โปรแกรมจำลองที่ปรับขนาดได้พร้อมตัวเลือกโทรศัพท์ กางออก แท็บเล็ต และเดสก์ท็อป

หากคุณไม่คุ้นเคยกับ Compose โปรดลองทำ Codelab พื้นฐานของ Jetpack Compose ก่อนทำ Codelab นี้

สิ่งที่คุณจะสร้าง

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

การสาธิตการรองรับอุปกรณ์หลายเครื่องที่คุณจะได้รับในโค้ดแล็บนี้

2. ตั้งค่า

หากต้องการรับโค้ดสำหรับ Codelab นี้ ให้โคลนที่เก็บ GitHub จากบรรทัดคำสั่งโดยใช้คำสั่งต่อไปนี้

git clone https://github.com/android/codelab-android-compose.git
cd codelab-android-compose/AdaptiveUiCodelab

หรือจะดาวน์โหลดที่เก็บเป็นไฟล์ ZIP ก็ได้

เราขอแนะนำให้คุณเริ่มจากโค้ดในสาขา main และทำตาม Codelab ทีละขั้นตอนตามที่คุณสะดวก

เปิดโปรเจ็กต์ใน Android Studio

  1. ในหน้าต่าง Welcome to Android Studio ให้เลือก c01826594f360d94.pngOpen an Existing Project
  2. เลือกโฟลเดอร์ <Download Location>/AdaptiveUiCodelab (ตรวจสอบว่าคุณเลือกไดเรกทอรี AdaptiveUiCodelab ที่มี build.gradle)
  3. เมื่อ Android Studio นำเข้าโปรเจ็กต์แล้ว ให้ทดสอบว่าคุณเรียกใช้กิ่ง main ได้

สำรวจโค้ดเริ่มต้น

โค้ดสาขาหลักมีแพ็กเกจ ui คุณจะทำงานกับไฟล์ต่อไปนี้ในแพ็กเกจนั้น

  • MainActivity.kt - กิจกรรมจุดแรกเข้าที่คุณเริ่มแอป
  • ReplyApp.kt - มี Composable ของ UI หน้าจอหลัก
  • ReplyHomeViewModel.kt - จัดเตรียมข้อมูลและสถานะ UI สำหรับเนื้อหาแอป
  • ReplyListContent.kt - มี Composable สำหรับแสดงรายการและหน้าจอรายละเอียด

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

หน้าจอเริ่มต้นในโทรศัพท์

มุมมองเริ่มต้นที่ยืดออกบนแท็บเล็ต

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

3. ทำให้แอปปรับเปลี่ยนได้

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

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

ขนาดหน้าต่าง

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

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

WindowWidthSizeClass สำหรับความกว้างแบบกะทัดรัด ปานกลาง และขยาย

WindowHeightSizeClass สำหรับความสูงแบบกะทัดรัด ปานกลาง และขยาย

ทั้งความกว้างและความสูงจะได้รับการจัดประเภทแยกกัน ดังนั้นในทุกช่วงเวลา แอปจะมี Window Size Classes 2 รายการ ได้แก่ รายการหนึ่งสำหรับความกว้างและอีกรายการหนึ่งสำหรับความสูง โดยปกติแล้ว ความกว้างที่ใช้ได้จะมีความสำคัญมากกว่าความสูงที่ใช้ได้เนื่องจากการเลื่อนแนวตั้งเป็นเรื่องปกติ ดังนั้นในกรณีนี้ คุณจะต้องใช้คลาสขนาดความกว้างด้วย

สถานะการพับ

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

ท่าทางของอุปกรณ์ที่พับได้ในรูปแบบแบนราบและกึ่งเปิด

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

อ่านเพิ่มเติมเกี่ยวกับท่าทางและบานพับเมื่อพับ

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

รับข้อมูลแบบปรับอัตโนมัติ

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

  1. เพิ่มรายการสำหรับอาร์ติแฟกต์นี้และเวอร์ชันของอาร์ติแฟกต์ลงในไฟล์แคตตาล็อกเวอร์ชัน

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0"

[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
  1. ในไฟล์บิลด์ของโมดูลแอป ให้เพิ่มทรัพยากร Dependency ของไลบรารีใหม่ แล้วซิงค์ Gradle โดยทำดังนี้

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

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

คุณลองใช้ฟีเจอร์นี้ได้แล้วใน MainActivity

  1. ใน onCreate() ภายในบล็อก ReplyTheme ให้รับข้อมูลการปรับหน้าต่างและแสดงคลาสขนาดใน Text ที่ใช้ร่วมกันได้ คุณเพิ่มข้อความนี้หลังองค์ประกอบ ReplyApp() ได้

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )

            val adaptiveInfo = currentWindowAdaptiveInfo()
            val sizeClassText =
                "${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
                "${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
            Text(
                text = sizeClassText,
                color = Color.Magenta,
                modifier = Modifier.padding(
                    WindowInsets.safeDrawing.asPaddingValues()
                )
            )
        }
    }
}

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

4. การนำทางแบบไดนามิก

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

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

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

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

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

การนำทางด้านล่าง

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

แถบการนำทางด้านล่างพร้อมรายการ

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

แถบนำทางด้านข้างพร้อมรายการต่างๆ

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

ลิ้นชักการนำทางแบบโมดอล

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

ลิ้นชักการนำทางแบบโมดัลพร้อมรายการต่างๆ

ลิ้นชักการนำทางถาวร

คุณสามารถใช้แผงการนำทางถาวรสำหรับการนำทางแบบคงที่ในแท็บเล็ตขนาดใหญ่, Chromebook และเดสก์ท็อป

ลิ้นชักการนำทางถาวรพร้อมรายการต่างๆ

ใช้การนำทางแบบไดนามิก

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

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

  1. เพิ่มทรัพยากร Dependency ของ Gradle เพื่อรับคอมโพเนนต์นี้โดยการอัปเดตแคตตาล็อกเวอร์ชันและสคริปต์บิลด์ของแอป จากนั้นซิงค์ Gradle

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0"

[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.navigation.suite)
}
  1. ค้นหาฟังก์ชันที่ใช้ร่วมกันได้ ReplyNavigationWrapper() ใน ReplyApp.kt แล้วแทนที่ Column และเนื้อหาด้วย NavigationSuiteScaffold ดังนี้

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            ReplyDestination.entries.forEach {
                item(
                    selected = it == selectedDestination,
                    onClick = { /*TODO update selection*/ },
                    icon = {
                        Icon(
                            imageVector = it.icon,
                            contentDescription = stringResource(it.labelRes)
                        )
                    },
                    label = {
                        Text(text = stringResource(it.labelRes))
                    },
                )
            }
        }
    ) {
        content()
    }
}

อาร์กิวเมนต์ navigationSuiteItems คือบล็อกที่ให้คุณเพิ่มรายการโดยใช้ฟังก์ชัน item() ซึ่งคล้ายกับการเพิ่มรายการใน LazyColumn ภายใน Lambda ต่อท้าย โค้ดนี้จะเรียกใช้ content() ที่ส่งผ่านเป็นอาร์กิวเมนต์ไปยัง ReplyNavigationWrapperUI()

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

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

  1. เพิ่มโค้ดต่อไปนี้ก่อนการเรียกใช้ NavigationSuiteScaffold:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    val windowSize = with(LocalDensity.current) {
        currentWindowSize().toSize().toDpSize()
    }
    val layoutType = if (windowSize.width >= 1200.dp) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
            currentWindowAdaptiveInfo()
        )
    }

    NavigationSuiteScaffold(
        layoutType = layoutType,
        ...
    ) {
        content()
    }
}

โค้ดนี้จะรับขนาดหน้าต่างก่อน แล้วแปลงเป็นหน่วย DP โดยใช้ currentWindowSize() และ LocalDensity.current จากนั้นจะเปรียบเทียบความกว้างของหน้าต่างเพื่อกำหนดประเภทเลย์เอาต์ของ UI การนำทาง หากความกว้างของหน้าต่างมีอย่างน้อย 1200.dp ระบบจะใช้ NavigationSuiteType.NavigationDrawer ไม่เช่นนั้น ระบบจะกลับไปใช้การคำนวณเริ่มต้น

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

แสดงการเปลี่ยนแปลงความสามารถในการปรับตัวสำหรับอุปกรณ์ขนาดต่างๆ

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

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

5. การใช้พื้นที่หน้าจอ

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

Material 3 กำหนดเลย์เอาต์มาตรฐาน 3 แบบ ซึ่งแต่ละแบบมีการกำหนดค่าสำหรับ Window Size Classes แบบกะทัดรัด ปานกลาง และขยาย เลย์เอาต์ Canonical ของรายละเอียดรายการเหมาะอย่างยิ่งสำหรับกรณีการใช้งานนี้ และพร้อมใช้งานใน Compose เป็น ListDetailPaneScaffold

  1. รับคอมโพเนนต์นี้โดยเพิ่มทรัพยากร Dependency ต่อไปนี้และทำการซิงค์ Gradle

gradle/libs.versions.toml

[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.adaptive.navigation)
}
  1. ค้นหาฟังก์ชันที่ประกอบกันได้ ReplyAppContent() ใน ReplyApp.kt ซึ่งปัจจุบันจะแสดงเฉพาะแผงรายการโดยการเรียกใช้ ReplyListPane() แทนที่การติดตั้งใช้งานนี้ด้วย ListDetailPaneScaffold โดยการแทรกโค้ดต่อไปนี้ เนื่องจากนี่เป็น API เวอร์ชันทดลอง คุณจึงต้องเพิ่มคำอธิบายประกอบ @OptIn ในฟังก์ชัน ReplyAppContent() ด้วย

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            ReplyListPane(replyHomeUIState, onEmailClick)
        },
        detailPane = {
            ReplyDetailPane(replyHomeUIState.emails.first())
        }
    )
}

โค้ดนี้จะสร้าง Navigator ก่อนโดยใช้ rememberListDetailPaneNavigator() แถบนำทางช่วยให้คุณควบคุมได้ว่าจะแสดงแผงใดและควรแสดงเนื้อหาใดในแผงนั้น ซึ่งจะแสดงให้ดูในภายหลัง

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

พารามิเตอร์ที่เหลือที่จำเป็นคือ Composable Lambdas สำหรับบานหน้าต่าง ReplyListPane() และ ReplyDetailPane() (อยู่ใน ReplyListContent.kt) ใช้เพื่อกรอกบทบาทของแผงรายการและแผงรายละเอียดตามลำดับ ReplyDetailPane() คาดหวังอาร์กิวเมนต์อีเมล ดังนั้นตอนนี้โค้ดนี้จึงใช้อีเมลแรกจากรายการอีเมลใน ReplyHomeUIState

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

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

  1. เปิด ReplyHomeViewModel.kt แล้วค้นหาคลาสข้อมูล ReplyHomeUIState เพิ่มพร็อพเพอร์ตี้สำหรับอีเมลที่เลือก โดยมีค่าเริ่มต้นเป็น null ดังนี้

ReplyHomeViewModel.kt

data class ReplyHomeUIState(
    val emails : List<Email> = emptyList(),
    val selectedEmail: Email? = null,
    val loading: Boolean = false,
    val error: String? = null
)
  1. ในไฟล์เดียวกัน ReplyHomeViewModel มีฟังก์ชัน setSelectedEmail() ที่จะเรียกใช้เมื่อผู้ใช้แตะรายการ แก้ไขฟังก์ชันนี้เพื่อคัดลอกสถานะ UI และบันทึกอีเมลที่เลือก

ReplyHomeViewModel.kt

fun setSelectedEmail(email: Email) {
    _uiState.update {
        it.copy(selectedEmail = email)
    }
}

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

  1. แก้ไขฟังก์ชัน observeEmails() ในไฟล์เดียวกัน เมื่อโหลดรายการอีเมลแล้ว หากสถานะ UI ก่อนหน้าไม่มีอีเมลที่เลือก ให้ตั้งค่าเป็นรายการแรก

ReplyHomeViewModel.kt

private fun observeEmails() {
    viewModelScope.launch {
        emailsRepository.getAllEmails()
            .catch { ex ->
                _uiState.value = ReplyHomeUIState(error = ex.message)
            }
            .collect { emails ->
                val currentSelection = _uiState.value.selectedEmail
                _uiState.value = ReplyHomeUIState(
                    emails = emails,
                    selectedEmail = currentSelection ?: emails.first()
                )
            }
    }
}
  1. กลับไปที่ ReplyApp.kt แล้วใช้อีเมลที่เลือก (หากมี) เพื่อป้อนเนื้อหาในแผงรายละเอียด

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    detailPane = {
        if (replyHomeUIState.selectedEmail != null) {
            ReplyDetailPane(replyHomeUIState.selectedEmail)
        }
    }
)

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

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

  1. หากต้องการแก้ไข ให้แทรกโค้ดต่อไปนี้เป็น Lambda ที่ส่งไปยัง ReplyListPane

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

Lambda นี้ใช้ Navigator ที่สร้างไว้ก่อนหน้านี้เพื่อเพิ่มลักษณะการทำงานเพิ่มเติมเมื่อมีการคลิกรายการ โดยจะเรียกใช้ Lambda เดิมที่ส่งไปยังฟังก์ชันนี้ จากนั้นจะเรียกใช้ navigator.navigateTo() เพื่อระบุว่าควรแสดงบานหน้าต่างใด แต่ละบานหน้าต่างในโครงร่างมีบทบาทที่เชื่อมโยงอยู่ และสำหรับบานหน้าต่างรายละเอียดคือ ListDetailPaneScaffoldRole.Detail ในหน้าต่างขนาดเล็ก การดำเนินการนี้จะทำให้ดูเหมือนว่าแอปได้ไปยังหน้าถัดไปแล้ว

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

  1. รองรับการนำทางย้อนกลับโดยเพิ่มโค้ดต่อไปนี้

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                ReplyListPane(
                    replyHomeUIState = replyHomeUIState,
                    onEmailClick = { email ->
                        onEmailClick(email)
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                if (replyHomeUIState.selectedEmail != null) {
                    ReplyDetailPane(replyHomeUIState.selectedEmail)
                }
            }
        }
    )
}

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

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

แสดงการเปลี่ยนแปลงความสามารถในการปรับตัวสำหรับอุปกรณ์ขนาดต่างๆ

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

6. ขอแสดงความยินดี

ยินดีด้วย คุณทำ Codelab นี้เสร็จสมบูรณ์แล้ว และได้เรียนรู้วิธีสร้างแอปที่ปรับเปลี่ยนตามอุปกรณ์ต่างๆ ด้วย Jetpack Compose

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

ขั้นตอนต่อไปคืออะไร

ดู Codelab อื่นๆ ในเส้นทาง Compose

แอปตัวอย่าง

  • ตัวอย่าง Compose คือชุดแอปจำนวนมากที่รวมแนวทางปฏิบัติแนะนำที่อธิบายไว้ใน Codelab

เอกสารอ้างอิง