1. บทนำ
ใน Codelab นี้ คุณจะได้เรียนรู้วิธีสร้างแอปที่ปรับขนาดได้สำหรับโทรศัพท์ แท็บเล็ต และอุปกรณ์แบบพับได้ รวมถึงวิธีปรับปรุงความสามารถในการเข้าถึงด้วย Jetpack Compose นอกจากนี้ คุณจะได้เรียนรู้แนวทางปฏิบัติแนะนำในการใช้คอมโพเนนต์และธีมของ Material 3 ด้วย
ก่อนที่จะเจาะลึกรายละเอียด คุณควรเข้าใจว่าความสามารถในการปรับตัวหมายถึงอะไร
ความสามารถในการปรับตัว
UI ของแอปควรตอบสนองต่อขนาดหน้าต่าง การวางแนว และรูปแบบของอุปกรณ์ที่แตกต่างกัน เลย์เอาต์แบบปรับอัตโนมัติจะเปลี่ยนแปลงไปตามพื้นที่หน้าจอที่มี การเปลี่ยนแปลงเหล่านี้มีตั้งแต่การปรับเลย์เอาต์แบบง่ายๆ เพื่อเพิ่มพื้นที่ว่าง การเลือกรูปแบบการนำทางที่เกี่ยวข้อง ไปจนถึงการเปลี่ยนเลย์เอาต์ทั้งหมดเพื่อใช้พื้นที่เพิ่มเติม
ดูข้อมูลเพิ่มเติมได้ที่การออกแบบที่ปรับอัตโนมัติ
ใน Codelab นี้ คุณจะได้สำรวจวิธีใช้และคำนึงถึงความสามารถในการปรับใช้เมื่อใช้ Jetpack Compose คุณสร้างแอปพลิเคชันที่เรียกว่า "ตอบกลับ" ขึ้นมา ซึ่งจะแสดงวิธีใช้ความสามารถในการปรับตัวสำหรับหน้าจอทุกประเภท และวิธีการทำงานร่วมกันและความสามารถในการเข้าถึงเพื่อมอบประสบการณ์ที่ดีที่สุดแก่ผู้ใช้
สิ่งที่คุณจะได้เรียนรู้
- วิธีออกแบบแอปเพื่อกำหนดเป้าหมายหน้าต่างทุกขนาดด้วย Jetpack Compose
- วิธีกำหนดเป้าหมายแอปของคุณสำหรับอุปกรณ์แบบพับได้แบบต่างๆ
- วิธีใช้การนำทางประเภทต่างๆ เพื่อการเข้าถึงและการช่วยเหลือพิเศษที่ดียิ่งขึ้น
- วิธีใช้คอมโพเนนต์ Material 3 เพื่อมอบประสบการณ์ที่ดีที่สุดสำหรับหน้าต่างทุกขนาด
สิ่งที่ต้องมี
- Android Studio เวอร์ชันเสถียรล่าสุด
- อุปกรณ์เสมือน Android 13 ที่ปรับขนาดได้
- ความรู้เกี่ยวกับ Kotlin
- ความเข้าใจเบื้องต้นเกี่ยวกับการเขียน (เช่น คำอธิบายประกอบ
@Composable
) - ความคุ้นเคยกับเลย์เอาต์การเขียน (เช่น
Row
และColumn
) ในระดับเบื้องต้น - มีความคุ้นเคยกับตัวแก้ไขเบื้องต้น (เช่น
Modifier.padding()
)
คุณจะใช้โปรแกรมจำลองที่ปรับขนาดได้สำหรับ Codelab นี้ซึ่งจะช่วยให้คุณสลับไปมาระหว่างอุปกรณ์ประเภทต่างๆ และขนาดหน้าต่างได้
หากคุณไม่คุ้นเคยกับ Compose ให้ลองใช้ Codelab พื้นฐานสำหรับ Jetpack Compose ก่อนที่จะดำเนินการ Codelab นี้
สิ่งที่คุณจะสร้าง
- แอปโปรแกรมรับส่งอีเมลแบบอินเทอร์แอกทีฟชื่อ "ตอบกลับ" ซึ่งใช้แนวทางปฏิบัติแนะนำสำหรับการออกแบบที่ปรับเปลี่ยนได้ การนำทางใน Material แบบต่างๆ และการใช้พื้นที่หน้าจออย่างเหมาะสม
2. ตั้งค่า
หากต้องการรับโค้ดสำหรับ Codelab ให้โคลนที่เก็บ GitHub จากบรรทัดคำสั่งนี้
git clone https://github.com/android/codelab-android-compose.git cd codelab-android-compose/AdaptiveUiCodelab
หรือจะดาวน์โหลดที่เก็บเป็นไฟล์ ZIP ก็ได้ โดยทำดังนี้
เราขอแนะนำให้คุณเริ่มจากโค้ดใน Branch หลัก แล้วทำตาม Codelab ทีละขั้นตอนได้ตามต้องการ
เปิดโปรเจ็กต์ใน Android Studio
- ในหน้าต่างยินดีต้อนรับสู่ Android Studio ให้เลือก
เปิดโปรเจ็กต์ที่มีอยู่
- เลือกโฟลเดอร์
<Download Location>/AdaptiveUiCodelab
(ตรวจสอบว่าเลือกไดเรกทอรีAdaptiveUiCodelab
ที่มีbuild.gradle
) - เมื่อ Android Studio นำเข้าโปรเจ็กต์แล้ว ให้ทดสอบว่าคุณเรียกใช้ Branch
main
ได้
สำรวจโค้ดเริ่มต้น
รหัสสาขา main จะมีแพ็กเกจ ui
คุณจะใช้ไฟล์ต่อไปนี้ในแพ็กเกจนั้นได้
MainActivity.kt
- กิจกรรมของจุดแรกเข้าที่คุณเริ่มแอปReplyApp.kt
- มี Composable ของ UI หน้าจอหลักReplyHomeViewModel.kt
- ระบุข้อมูลและสถานะ UI สำหรับเนื้อหาแอปReplyListContent.kt
- มี Composable สำหรับระบุรายการและหน้าจอรายละเอียด
หากคุณเรียกใช้แอปนี้ในโปรแกรมจำลองที่ปรับขนาดได้และลองใช้อุปกรณ์ประเภทอื่น เช่น โทรศัพท์หรือแท็บเล็ต UI จะขยายให้เต็มพื้นที่แทนที่จะใช้ประโยชน์จากพื้นที่หน้าจอหรือให้ความสามารถในการเข้าถึงที่เข้าถึงง่าย
คุณจะอัปเดตแอปเพื่อใช้ประโยชน์จากพื้นที่หน้าจอ เพิ่มความสามารถในการใช้งาน และปรับปรุงประสบการณ์โดยรวมของผู้ใช้
3. ทำให้แอปปรับเปลี่ยนได้
ส่วนนี้จะอธิบายถึงความหมายของการทำให้แอปปรับเปลี่ยนได้ และองค์ประกอบที่ Material 3 มีให้เพื่อช่วยให้การดำเนินการดังกล่าวง่ายขึ้น นอกจากนี้ยังครอบคลุมประเภทหน้าจอและสถานะที่คุณจะกำหนดเป้าหมาย ซึ่งรวมถึงโทรศัพท์ แท็บเล็ต แท็บเล็ตขนาดใหญ่ และอุปกรณ์แบบพับได้
คุณจะเริ่มต้นด้วยการอธิบายพื้นฐานของขนาดหน้าต่าง ลักษณะการพับ และตัวเลือกการนำทางประเภทต่างๆ จากนั้นคุณสามารถใช้ API เหล่านี้ในแอปเพื่อปรับแอปให้ปรับตัวได้มากขึ้น
ขนาดหน้าต่าง
อุปกรณ์ Android มีทุกรูปแบบและทุกขนาด ตั้งแต่โทรศัพท์ไปจนถึงอุปกรณ์แบบพับได้ไปจนถึงแท็บเล็ตและอุปกรณ์ ChromeOS UI ต้องปรับเปลี่ยนตามอุปกรณ์และปรับเปลี่ยนได้ เพื่อรองรับขนาดหน้าต่างมากที่สุดเท่าที่จะเป็นไปได้ เราได้กำหนดค่าเบรกพอยท์ที่ช่วยจำแนกอุปกรณ์ออกเป็นคลาสขนาดที่กำหนดไว้ล่วงหน้า (กะทัดรัด ปานกลาง และขยาย) ที่เรียกว่าคลาสขนาดหน้าต่างเพื่อช่วยคุณค้นหาเกณฑ์ที่ถูกต้องสำหรับเปลี่ยน UI ของแอป ตำแหน่งเหล่านี้คือชุดเบรกพอยท์ของวิวพอร์ตตามความคิดเห็นที่จะช่วยคุณออกแบบ พัฒนา และทดสอบเลย์เอาต์ของแอปพลิเคชันที่ปรับเปลี่ยนได้และปรับเปลี่ยนตามอุปกรณ์
หมวดหมู่ดังกล่าวได้รับการคัดสรรมาโดยเฉพาะเพื่อสร้างความสมดุลระหว่างความเรียบง่ายของเลย์เอาต์ รวมถึงการเพิ่มประสิทธิภาพแอปให้เหมาะกับกรณีที่ไม่ซ้ำต่างๆ ได้อย่างยืดหยุ่น คลาสขนาดหน้าต่างจะกำหนดโดยพื้นที่หน้าจอที่มีในแอปเสมอ ซึ่งอาจไม่ใช่พื้นที่จริงทั้งหน้าจอสำหรับการทำหลายอย่างพร้อมกันหรือการแบ่งกลุ่มอื่นๆ
ทั้งความกว้างและความสูงมีการจัดประเภทแยกกัน ดังนั้นไม่ว่าช่วงเวลาใด แอปของคุณจะมีคลาสขนาดหน้าต่าง 2 คลาส คือสำหรับความกว้างและความสูง ความกว้างที่ใช้ได้มักจะสำคัญกว่าความสูงที่มีอยู่เนื่องจากการเลื่อนในแนวตั้งที่พบได้ทั่วไป ดังนั้นในกรณีนี้คุณจะใช้คลาสขนาดความกว้างด้วย
พับสถานะ
อุปกรณ์แบบพับได้ยังมีสถานการณ์อื่นๆ ที่แอปของคุณสามารถปรับให้เข้ากับอุปกรณ์ที่มีขนาดแตกต่างกันและมีบานพับในตัว บานพับอาจบดบังบางส่วนของจอแสดงผล ทําให้พื้นที่ดังกล่าวไม่เหมาะที่จะแสดงเนื้อหา อุปกรณ์อาจแยกออกจากกัน ซึ่งหมายความว่าจะมีจอแสดงผลแยกกัน 2 หน้าจอเมื่อกางอุปกรณ์ออก
นอกจากนี้ ผู้ใช้อาจมองหน้าจอด้านในขณะที่บานพับเปิดอยู่บางส่วน ผลที่ได้คือลักษณะทางกายภาพที่แตกต่างกันตามการวางแนวพับ ซึ่งก็คือการวางตั้งบนโต๊ะ (เส้นพับแนวนอน แสดงทางด้านขวาในภาพด้านบน) และลักษณะการวางหนังสือ (เส้นพับในแนวตั้ง)
อ่านเพิ่มเติมเกี่ยวกับลักษณะการพับและบานพับ
ข้อใดต่อไปนี้คือสิ่งที่ต้องพิจารณาเมื่อใช้เลย์เอาต์แบบปรับขนาดได้ซึ่งรองรับอุปกรณ์แบบพับได้
รับข้อมูลที่ปรับเปลี่ยนได้
ไลบรารี Material3 adaptive
ช่วยให้คุณเข้าถึงข้อมูลเกี่ยวกับหน้าต่างที่แอปทำงานอยู่ได้อย่างสะดวก
- เพิ่มรายการสำหรับอาร์ติแฟกต์นี้และเวอร์ชันของอาร์ติแฟกต์ไปยังไฟล์แคตตาล็อกเวอร์ชัน:
gradle/libs.versions.toml
[versions]
material3Adaptive = "1.0.0-beta01"
[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
- ในไฟล์บิลด์ของโมดูลแอป ให้เพิ่มทรัพยากร Dependency ของไลบรารีใหม่ แล้วซิงค์ Gradle ดังนี้
app/build.gradle.kts
dependencies {
implementation(libs.androidx.material3.adaptive)
}
ในตอนนี้ คุณสามารถใช้ currentWindowAdaptiveInfo()
ในขอบเขตที่ประกอบกันได้ เพื่อรับออบเจ็กต์ WindowAdaptiveInfo
ที่มีข้อมูล เช่น คลาสขนาดหน้าต่างปัจจุบัน และดูว่าอุปกรณ์อยู่ในลักษณะพับได้เหมือนอยู่บนโต๊ะหรือไม่
คุณลองดำเนินการนี้ได้แล้วใน MainActivity
- ใน
onCreate()
ภายในบล็อกReplyTheme
ให้รับข้อมูลการปรับหน้าต่างที่แสดงได้และแสดงคลาสขนาดใน ComposableText
คุณเพิ่มข้อมูลนี้ได้หลังจากองค์ประกอบ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(20.dp)
)
}
}
}
การเรียกใช้แอปตอนนี้จะแสดงคลาสขนาดหน้าต่างที่พิมพ์บนเนื้อหาแอป คุณสามารถดูสิ่งอื่นๆ ที่มีให้ในหน้าต่างข้อมูลแบบปรับเปลี่ยนได้ หลังจากนั้นคุณนํา Text
นี้ออกได้เนื่องจากครอบคลุมเนื้อหาของแอป และไม่จําเป็นต้องทําขั้นตอนถัดไป
4. การนำทางแบบไดนามิก
ต่อไปนี้คุณจะต้องปรับการไปยังส่วนต่างๆ ของแอปเมื่อมีการเปลี่ยนแปลงสถานะและขนาดของอุปกรณ์ เพื่อให้แอปใช้งานได้ง่ายขึ้น
เมื่อผู้ใช้ถือโทรศัพท์ โดยปกติแล้วนิ้วจะอยู่ที่ด้านล่างของหน้าจอ เมื่อผู้ใช้ถืออุปกรณ์แบบพับได้ที่เปิดอยู่หรือแท็บเล็ต โดยปกติแล้วนิ้วจะอยู่ใกล้กับด้านข้าง ผู้ใช้ควรไปยังส่วนต่างๆ หรือเริ่มการโต้ตอบกับแอปได้โดยไม่ต้องใช้มือจับที่สุดหรือเปลี่ยนตำแหน่งมือ
ขณะที่คุณออกแบบแอปและตัดสินใจว่าจะวางองค์ประกอบ UI แบบอินเทอร์แอกทีฟไว้ที่ใดในเลย์เอาต์ ให้พิจารณาผลกระทบด้านสรีรศาสตร์ของแต่ละพื้นที่ของหน้าจอ
- พื้นที่ใดที่เข้าถึงได้สะดวกขณะถืออุปกรณ์
- บริเวณใดที่เข้าถึงได้ด้วยการยืดนิ้วเท่านั้น ซึ่งอาจไม่สะดวก
- บริเวณใดเข้าถึงได้ยากหรืออยู่ไกลจากจุดที่ผู้ใช้ถืออุปกรณ์อยู่
การนำทางเป็นสิ่งแรกที่ผู้ใช้โต้ตอบด้วยและมีการดำเนินการที่มีความสำคัญสูงซึ่งเกี่ยวข้องกับเส้นทางที่สำคัญของผู้ใช้ คุณจึงควรวางการนำทางไว้ในบริเวณที่จะเข้าถึงได้ง่ายที่สุด ไลบรารีแบบปรับได้ของ Material มีคอมโพเนนต์หลายอย่างที่ช่วยให้คุณนำการนำทางไปใช้ได้ โดยขึ้นอยู่กับคลาสขนาดหน้าต่างของอุปกรณ์
Bottom Navigation
การนำทางด้านล่างเหมาะสำหรับขนาดกะทัดรัด เนื่องจากโดยปกติเราจะถืออุปกรณ์ซึ่งนิ้วโป้งของเราสามารถเข้าถึงจุดสัมผัสการนำทางด้านล่างทั้งหมดได้โดยง่าย ใช้ตัวเลือกนี้เมื่อใดก็ตามที่อุปกรณ์มีขนาดกะทัดรัดหรือพับได้ในขนาดที่กะทัดรัดเมื่อพับอยู่
แถบข้างสำหรับไปยังส่วนต่างๆ
สำหรับหน้าต่างที่มีความกว้างปานกลาง แถบนำทางนี้เหมาะอย่างยิ่งสำหรับความสามารถในการเข้าถึงเนื่องจากนิ้วหัวแม่มือของเราวางอยู่ข้างๆ อุปกรณ์โดยธรรมชาติ นอกจากนี้ คุณยังรวมแถบการนำทางเข้ากับลิ้นชักการนำทางเพื่อแสดงข้อมูลเพิ่มเติมได้ด้วย
ลิ้นชักการนำทาง
ลิ้นชักการนำทางเป็นวิธีง่ายๆ ในการดูข้อมูลโดยละเอียดสำหรับแท็บการนำทาง และสามารถเข้าถึงได้อย่างง่ายดายเมื่อใช้แท็บเล็ตหรืออุปกรณ์ขนาดใหญ่ ลิ้นชักการนำทางมี 2 ประเภท ได้แก่ ลิ้นชักการนำทางแบบโมดัลและลิ้นชักการนำทางแบบถาวร
ลิ้นชักการนำทางสำหรับคำสั่ง
คุณสามารถใช้ลิ้นชักการนำทางแบบโมดัลสำหรับโทรศัพท์และแท็บเล็ตขนาดกะทัดรัดถึงขนาดกลาง เพราะสามารถขยายหรือซ่อนเพื่อวางซ้อนบนเนื้อหาได้ ซึ่งบางครั้งอาจใช้ร่วมกับรางนำทางได้
ลิ้นชักการนำทางถาวร
คุณสามารถใช้ลิ้นชักการนำทางถาวรสำหรับการนำทางแบบคงที่ในแท็บเล็ตขนาดใหญ่, Chromebook และเดสก์ท็อป
ใช้การนำทางแบบไดนามิก
ในตอนนี้ คุณจะสลับใช้การนำทางประเภทต่างๆ เมื่อสถานะของอุปกรณ์และขนาดมีการเปลี่ยนแปลง
ปัจจุบันแอปจะแสดง NavigationBar
ใต้เนื้อหาหน้าจอเสมอไม่ว่าอุปกรณ์จะมีสถานะใดก็ตาม แต่คุณสามารถใช้คอมโพเนนต์ Material NavigationSuiteScaffold
เพื่อสลับระหว่างคอมโพเนนต์การนําทางต่างๆ โดยอัตโนมัติตามข้อมูล เช่น คลาสขนาดหน้าต่างปัจจุบัน
- เพิ่มการอ้างอิง Gradle เพื่อรับคอมโพเนนต์นี้โดยการอัปเดตแคตตาล็อกเวอร์ชันและสคริปต์บิลด์ของแอป จากนั้นทำการซิงค์ Gradle ตามขั้นตอนต่อไปนี้
gradle/libs.versions.toml
[versions]
material3AdaptiveNavSuite = "1.3.0-beta01"
[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)
}
- ค้นหาฟังก์ชัน Composable
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
ปัจจุบันก็ตาม อย่างไรก็ตาม คุณสามารถดำเนินการดังกล่าวได้ด้วยการเปลี่ยนแปลงเพียงเล็กน้อย
- เพิ่มโค้ดต่อไปนี้ก่อนโทรหา
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. การใช้พื้นที่หน้าจอ
ไม่ว่าคุณจะเรียกใช้แอปบนแท็บเล็ตขนาดเล็ก อุปกรณ์ที่กางออก หรือแท็บเล็ตขนาดใหญ่ หน้าจอจะยืดเต็มพื้นที่ที่เหลือ คุณต้องตรวจสอบว่าสามารถใช้ประโยชน์จากพื้นที่หน้าจอนั้นเพื่อแสดงข้อมูลเพิ่มเติม เช่น สำหรับแอปนี้ การแสดงอีเมลและชุดข้อความต่อผู้ใช้ในหน้าเดียวกัน
วัสดุ 3 จะกำหนดรูปแบบ Canonical จำนวน 3 รูปแบบ ซึ่งแต่ละรายการมีการกำหนดค่าสำหรับคลาสขนาดหน้าต่างที่กะทัดรัด ปานกลาง และขยาย รูปแบบตามรูปแบบบัญญัติรายละเอียดรายการเหมาะสำหรับกรณีการใช้งานนี้ และสามารถใช้ได้ในการเขียนเป็น ListDetailPaneScaffold
- รับคอมโพเนนต์นี้ด้วยการเพิ่มทรัพยากร 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)
}
- ค้นหาฟังก์ชัน Composable
ReplyAppContent()
ในReplyApp.kt
ซึ่งปัจจุบันแสดงเฉพาะแผงรายการโดยเรียกใช้ReplyListPane()
แทนที่การใช้งานนี้ด้วยListDetailPaneScaffold
ด้วยการแทรกโค้ดต่อไปนี้ เนื่องจาก API นี้เป็น 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())
}
)
}
โค้ดนี้จะสร้างตัวนำทางโดยใช้ rememberListDetailPaneNavigator
ก่อน ตัวนำทางจะช่วยควบคุมว่าจะแสดงแผงใด และเนื้อหาใดควรจะแสดงในแผงนั้น ซึ่งจะแสดงในภายหลัง
ListDetailPaneScaffold
จะแสดงแผง 2 ช่องเมื่อขยายคลาสขนาดความกว้างของหน้าต่าง มิฉะนั้นจะแสดง 1 แผงหรืออีกแผงตามค่าที่ระบุสำหรับพารามิเตอร์ 2 รายการ ได้แก่ คำสั่ง Scaffold และค่า Scaffold ในการรับลักษณะการทำงานเริ่มต้น โค้ดนี้ใช้คำสั่ง Scaffold และค่า Scaffold ที่ตัวนำทางระบุไว้
พารามิเตอร์ที่จําเป็นที่เหลือคือ lambda ที่ประกอบกันได้สําหรับแผง ReplyListPane()
และ ReplyDetailPane()
(พบได้ใน ReplyListContent.kt
) จะใช้เพื่อเติมบทบาทในรายการและแผงรายละเอียดตามลำดับ ReplyDetailPane()
คาดหวังอาร์กิวเมนต์อีเมล ดังนั้นโค้ดนี้จะใช้อีเมลฉบับแรกจากรายการอีเมลใน ReplyHomeUIState
เรียกใช้แอปและเปลี่ยนมุมมองโปรแกรมจำลองไปเป็นอุปกรณ์แบบพับได้หรือแท็บเล็ต (คุณอาจต้องเปลี่ยนการวางแนวด้วย) เพื่อดูเลย์เอาต์แบบ 2 ช่อง ดูดีกว่าก่อนหน้านี้มากเลย!
ทีนี้เราจะมาพูดถึงลักษณะการทำงานบางอย่างที่ต้องการของหน้าจอนี้ เมื่อผู้ใช้แตะอีเมลในแผงรายการ อีเมลควรจะปรากฏในแผงรายละเอียดพร้อมกับการตอบกลับทั้งหมด ขณะนี้ แอปจะไม่ติดตามว่าได้เลือกอีเมลใดไว้ และแตะที่รายการจะไม่ส่งผลใดๆ ที่ที่ดีที่สุดในการเก็บข้อมูลนี้ไว้คือสถานะ UI ที่เหลือใน ReplyHomeUIState
- เปิด
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
)
- ในไฟล์เดียวกัน
ReplyHomeViewModel
มีฟังก์ชันsetSelectedEmail()
ซึ่งจะเรียกใช้เมื่อผู้ใช้แตะรายการ แก้ไขฟังก์ชันนี้เพื่อคัดลอกสถานะ UI และบันทึกอีเมลที่เลือก ดังนี้
ReplyHomeViewModel.kt
fun setSelectedEmail(email: Email) {
_uiState.update {
it.copy(selectedEmail = email)
}
}
สิ่งที่ควรพิจารณาคือสิ่งที่เกิดขึ้นก่อนผู้ใช้แตะรายการใดก็ตามและอีเมลที่เลือกคือ null
ควรแสดงอะไรในแผงรายละเอียด การจัดการเคสนี้ทำได้หลายวิธี เช่น การแสดงรายการแรกในรายการโดยค่าเริ่มต้น
- แก้ไขฟังก์ชัน
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()
)
}
}
}
- กลับไปที่
ReplyApp.kt
แล้วใช้อีเมลที่เลือก (หากมี) เพื่อป้อนข้อมูลเนื้อหาในแผงรายละเอียด ดังนี้
ReplyApp.kt
ListDetailPaneScaffold(
// ...
detailPane = {
if (replyHomeUIState.selectedEmail != null) {
ReplyDetailPane(replyHomeUIState.selectedEmail)
}
}
)
เรียกใช้แอปอีกครั้งและเปลี่ยนโปรแกรมจำลองเป็นขนาดแท็บเล็ต และดูว่าการแตะที่รายการจะอัปเดตเนื้อหาในแผงรายละเอียด
วิธีนี้ทำงานได้ดีเมื่อมองเห็นทั้ง 2 แผง แต่เมื่อหน้าต่างมีพื้นที่ให้แสดงเพียงแผงเดียวเท่านั้น จึงดูเหมือนว่าไม่มีอะไรเกิดขึ้นเมื่อคุณแตะรายการ ลองเปลี่ยนมุมมองโปรแกรมจำลองไปเป็นโทรศัพท์ หรืออุปกรณ์แบบพับได้ในแนวตั้ง แล้วคุณจะเห็นเฉพาะช่องรายการเท่านั้นที่จะปรากฏขึ้นแม้จะแตะรายการแล้วก็ตาม เพราะแม้ว่าจะอัปเดตอีเมลที่เลือกแล้ว แต่ ListDetailPaneScaffold
จะยังคงโฟกัสที่แผงรายการในการกำหนดค่าเหล่านี้
- ในการแก้ไขปัญหานี้ ให้แทรกโค้ดต่อไปนี้เมื่อ lambda ที่ส่งไปยัง
ReplyListPane
ReplyApp.kt
ListDetailPaneScaffold(
// ...
listPane = {
ReplyListPane(
replyHomeUIState = replyHomeUIState,
onEmailClick = { email ->
onEmailClick(email)
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
}
)
},
// ...
)
lambda นี้ใช้ตัวนำทางที่สร้างขึ้นก่อนหน้านี้เพื่อเพิ่มลักษณะการทำงานเพิ่มเติมเมื่อมีการคลิกรายการ โดยจะเรียก lambda เดิมที่ส่งไปยังฟังก์ชันนี้ และยังเรียก navigator.navigateTo()
เพื่อระบุว่าควรแสดงแผงใด แต่ละแผงในนั่งร้านมีบทบาทที่เกี่ยวข้อง และสําหรับแผงรายละเอียดจะเป็น ListDetailPaneScaffoldRole.Detail
ในหน้าต่างขนาดเล็ก จะมีลักษณะที่แอปเลื่อนไปข้างหน้า
แอปยังต้องจัดการสิ่งที่จะเกิดขึ้นเมื่อผู้ใช้กดปุ่มย้อนกลับจากแผงรายละเอียด และลักษณะการทำงานนี้จะแตกต่างออกไปโดยขึ้นอยู่กับว่ามีช่องเดียวหรือ 2 ช่องที่มองเห็นได้
- สนับสนุนการนำทางกลับด้วยการเพิ่มรหัสต่อไปนี้
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)
}
}
}
)
}
ตัวนำทางจะทราบสถานะทั้งหมดของ ListDetailPaneScaffold
ว่าการนำทางย้อนกลับนั้นใช้ได้หรือไม่ และสิ่งที่ต้องทำในสถานการณ์ทั้งหมดเหล่านี้ โค้ดนี้จะสร้าง BackHandler
ซึ่งเปิดใช้งานเมื่อใดก็ตามที่ตัวนำทางสามารถย้อนกลับได้ และภายใน lambda จะเรียก navigateBack()
นอกจากนี้ แต่ละแผงจะรวมไว้ใน AnimatedPane()
Composable เพื่อให้การสลับระหว่างช่องต่างๆ ราบรื่นยิ่งขึ้น
เรียกใช้แอปอีกครั้งในโปรแกรมจำลองที่ปรับขนาดได้สำหรับอุปกรณ์ประเภทต่างๆ ทั้งหมด และสังเกตเห็นว่าเมื่อการกำหนดค่าหน้าจอมีการเปลี่ยนแปลง หรือคุณกางอุปกรณ์พับ เนื้อหาการนำทางและหน้าจอจะเปลี่ยนแปลงตามการเปลี่ยนแปลงสถานะของอุปกรณ์ นอกจากนี้ ให้ลองแตะอีเมลในแผงรายการ แล้วดูว่าเลย์เอาต์ทำงานเป็นอย่างไรในหน้าจอต่างๆ โดยแสดงทั้ง 2 ช่องแสดงข้างกัน หรือแสดงภาพเคลื่อนไหวระหว่างแผงได้อย่างราบรื่น
ยินดีด้วย คุณสร้างแอปให้เหมาะกับอุปกรณ์ทุกประเภทและทุกขนาดสำเร็จแล้ว ลองลองเล่นแอปในอุปกรณ์แบบพับได้ แท็บเล็ต หรืออุปกรณ์เคลื่อนที่อื่นๆ เลย
6. ขอแสดงความยินดี
ยินดีด้วย คุณศึกษา Codelab นี้จบแล้ว และได้เรียนรู้วิธีทำให้แอปปรับตัวได้ด้วย Jetpack Compose
คุณได้เรียนรู้วิธีตรวจสอบขนาดและสถานะการพับอุปกรณ์ แล้วอัปเดต UI, การไปยังส่วนต่างๆ และฟังก์ชันอื่นๆ ของแอปให้สอดคล้องกัน และคุณยังได้เรียนรู้ว่าความสามารถในการปรับตัวช่วยเพิ่มความสามารถในการเข้าถึงและปรับปรุงประสบการณ์ของผู้ใช้ได้อย่างไร
ขั้นตอนถัดไปคือ
ลองดู Codelab อื่นๆ ในเส้นทางการเขียน
แอปตัวอย่าง
- ตัวอย่างการเขียนคือคอลเล็กชันของแอปจำนวนมากที่มีแนวทางปฏิบัติแนะนำที่อธิบายไว้ใน Codelab