สร้างเลย์เอาต์แผงรองรับ

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

ดูรายละเอียดเพิ่มเติมได้ที่หลักเกณฑ์เกี่ยวกับแผงสนับสนุนของ Material 3

ใช้แผงสนับสนุนที่มีโครงสร้างพื้นฐาน

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

โครงสร้างของแผงสนับสนุนรองรับแผงได้สูงสุด 3 แผง ดังนี้

  • แผงหลัก: แสดงเนื้อหาหลัก
  • แผงสนับสนุน: ให้บริบทหรือเครื่องมือเพิ่มเติมที่เกี่ยวข้องกับแผงหลัก
  • แผงเพิ่มเติม (ไม่บังคับ): ใช้สำหรับเนื้อหาเสริมเมื่อจำเป็น

โครงสร้างจะปรับตามขนาดหน้าต่าง ดังนี้

  • ในหน้าต่างขนาดใหญ่ แผงหลักและแผงสนับสนุนจะปรากฏเคียงข้างกัน
  • ในหน้าต่างขนาดเล็ก จะมองเห็นได้ครั้งละ 1 แผงเท่านั้น โดยจะสลับไปตามการไปยังส่วนต่างๆ ของผู้ใช้

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

เพิ่มทรัพยากร Dependency

NavigableSupportingPaneScaffold เป็นส่วนหนึ่งของไลบรารีเลย์เอาต์แบบปรับอัตโนมัติของ Material 3

เพิ่มทรัพยากร Dependency ที่เกี่ยวข้อง 3 รายการต่อไปนี้ลงในไฟล์ build.gradle ของแอปหรือโมดูล

Kotlin

implementation("androidx.compose.material3.adaptive:adaptive")
implementation("androidx.compose.material3.adaptive:adaptive-layout")
implementation("androidx.compose.material3.adaptive:adaptive-navigation")

ดึงดูด

implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
  • adaptive: องค์ประกอบที่ใช้สร้างสรรค์ระดับต่ำ เช่น HingeInfo และ Posture

  • adaptive-layout: เลย์เอาต์แบบปรับขนาดได้ เช่น ListDetailPaneScaffold และ SupportingPaneScaffold

  • adaptive-navigation: Composable สำหรับการไปยังส่วนต่างๆ ภายในและระหว่างแผง รวมถึงเลย์เอาต์แบบปรับเปลี่ยนได้ที่รองรับการไปยังส่วนต่างๆ โดยค่าเริ่มต้น เช่น NavigableListDetailPaneScaffold และ NavigableSupportingPaneScaffold

ตรวจสอบว่าโปรเจ็กต์มี compose-material3-adaptive เวอร์ชัน 1.1.0-beta1 ขึ้นไป

เลือกใช้ท่าทางสัมผัสการย้อนกลับที่คาดการณ์ได้

หากต้องการเปิดใช้ภาพเคลื่อนไหวการย้อนกลับที่คาดการณ์ได้ใน Android 15 หรือต่ำกว่า คุณต้องเลือกใช้เพื่อรองรับท่าทางสัมผัสการย้อนกลับที่คาดการณ์ได้ หากต้องการเลือกใช้ ให้เพิ่ม android:enableOnBackInvokedCallback="true" ลงในแท็ก <application> หรือแท็ก <activity> แต่ละแท็กภายในไฟล์ AndroidManifest.xml

เมื่อแอปกำหนดเป้าหมายเป็น Android 16 (ระดับ API 36) ขึ้นไป ระบบจะเปิดใช้การย้อนกลับที่คาดการณ์ได้โดยค่าเริ่มต้น

สร้างตัวนำทาง

ในหน้าต่างขนาดเล็ก ระบบจะแสดงแผงทีละแผงเท่านั้น ดังนั้นให้ใช้ ThreePaneScaffoldNavigator เพื่อย้ายไปมาระหว่างแผง สร้างอินสแตนซ์ของเนวิเกเตอร์ด้วย rememberSupportingPaneScaffoldNavigator

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

ส่ง Navigator ไปยัง Scaffold

Scaffold ต้องใช้ ThreePaneScaffoldNavigator ซึ่งเป็นอินเทอร์เฟซที่แสดงสถานะของ Scaffold, ThreePaneScaffoldValue และ PaneScaffoldDirective

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = { /*...*/ },
    supportingPane = { /*...*/ },
)

แผงหลักและแผงสนับสนุนเป็น Composable ที่มีเนื้อหาของคุณ ใช้ AnimatedPane เพื่อใช้ภาพเคลื่อนไหวของแผงเริ่มต้นระหว่างการนำทาง ใช้ค่า Scaffold เพื่อตรวจสอบว่าแผงสนับสนุนซ่อนอยู่หรือไม่ หากซ่อนอยู่ ให้แสดงปุ่มที่เรียกใช้ navigateTo(SupportingPaneScaffoldRole.Supporting) เพื่อแสดงแผงสนับสนุน

สำหรับหน้าจอขนาดใหญ่ ให้ใช้เมธอด ThreePaneScaffoldNavigator.navigateBack() เพื่อปิดแผงสนับสนุน โดยส่งค่าคงที่ BackNavigationBehavior.PopUntilScaffoldValueChange การเรียกใช้เมธอดนี้จะบังคับให้มีการจัดองค์ประกอบใหม่ของ NavigableSupportingPaneScaffold ในระหว่างการจัดองค์ประกอบใหม่ ให้ตรวจสอบพร็อพเพอร์ตี้ ThreePaneScaffoldNavigator.currentDestination เพื่อพิจารณาว่าจะแสดงแผงสนับสนุนหรือไม่

ต่อไปนี้คือการติดตั้งใช้งาน Scaffold ที่สมบูรณ์

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()
val backNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = {
        AnimatedPane(
            modifier = Modifier
                .safeContentPadding()
                .background(Color.Red)
        ) {
            if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Hidden) {
                Button(
                    modifier = Modifier
                        .wrapContentSize(),
                    onClick = {
                        scope.launch {
                            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting)
                        }
                    }
                ) {
                    Text("Show supporting pane")
                }
            } else {
                Text("Supporting pane is shown")
            }
        }
    },
    supportingPane = {
        AnimatedPane(modifier = Modifier.safeContentPadding()) {
            Column {
                // Allow users to dismiss the supporting pane. Use back navigation to
                // hide an expanded supporting pane.
                if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Expanded) {
                    // Material design principles promote the usage of a right-aligned
                    // close (X) button.
                    IconButton(
                        modifier =  Modifier.align(Alignment.End).padding(16.dp),
                        onClick = {
                            scope.launch {
                                scaffoldNavigator.navigateBack(backNavigationBehavior)
                            }
                        }
                    ) {
                        Icon(Icons.Default.Close, contentDescription = "Close")
                    }
                }
                Text("Supporting pane")
            }

        }
    }
)

แยกคอมโพสิเบิลของแผง

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

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.MainPane(
    shouldShowSupportingPaneButton: Boolean,
    onNavigateToSupportingPane: () -> Unit,
    modifier: Modifier = Modifier,
) {
    AnimatedPane(
        modifier = modifier.safeContentPadding()
    ) {
        // Main pane content
        if (shouldShowSupportingPaneButton) {
            Button(onClick = onNavigateToSupportingPane) {
                Text("Show supporting pane")
            }
        } else {
            Text("Supporting pane is shown")
        }
    }
}

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.SupportingPane(
    scaffoldNavigator: ThreePaneScaffoldNavigator<Any>,
    modifier: Modifier = Modifier,
    backNavigationBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange,
) {
    val scope = rememberCoroutineScope()
    AnimatedPane(modifier = Modifier.safeContentPadding()) {
        Column {
            // Allow users to dismiss the supporting pane. Use back navigation to
            // hide an expanded supporting pane.
            if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Expanded) {
                // Material design principles promote the usage of a right-aligned
                // close (X) button.
                IconButton(
                    modifier =  modifier.align(Alignment.End).padding(16.dp),
                    onClick = {
                        scope.launch {
                            scaffoldNavigator.navigateBack(backNavigationBehavior)
                        }
                    }
                ) {
                    Icon(Icons.Default.Close, contentDescription = "Close")
                }
            }
            Text("Supporting pane")
        }

    }
}

การแยกแผงเป็น Composable ช่วยให้การใช้ SupportingPaneScaffold ง่ายขึ้น (เปรียบเทียบโค้ดต่อไปนี้กับการติดตั้งใช้งาน Scaffold ที่สมบูรณ์ในส่วนก่อนหน้า)

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = {
                scope.launch {
                    scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary)
                }
            }
        )
    },
    supportingPane = { SupportingPane(scaffoldNavigator = scaffoldNavigator) },
)

หากต้องการควบคุมแง่มุมที่เฉพาะเจาะจงของ Scaffold มากขึ้น ให้พิจารณาใช้ SupportingPaneScaffold แทน NavigableSupportingPaneScaffold ซึ่งจะยอมรับ PaneScaffoldDirective และ ThreePaneScaffoldValue หรือ ThreePaneScaffoldState แยกกัน ความยืดหยุ่นนี้ช่วยให้คุณใช้ตรรกะที่กำหนดเองสำหรับระยะห่างระหว่างแผงและกำหนดจำนวนแผงที่ควรแสดงพร้อมกันได้ นอกจากนี้ คุณยังเปิดใช้การรองรับการย้อนกลับที่คาดการณ์ได้โดยเพิ่ม ThreePaneScaffoldPredictiveBackHandler

เพิ่ม ThreePaneScaffoldPredictiveBackHandler

แนบตัวแฮนเดิลการย้อนกลับที่คาดการณ์ได้ซึ่งรับอินสแตนซ์ตัวนำทาง Scaffold และระบุ backBehavior ซึ่งจะเป็นตัวกำหนดวิธีนำปลายทางออกจาก Back Stack ระหว่างการนำทางย้อนกลับ จากนั้นส่ง scaffoldDirective และ scaffoldState ไปยัง SupportingPaneScaffold ใช้โอเวอร์โหลดที่ยอมรับ ThreePaneScaffoldState โดยส่งผ่าน scaffoldNavigator.scaffoldState

กำหนดแผงหลักและแผงสนับสนุนภายใน SupportingPaneScaffold ใช้ AnimatedPane สำหรับภาพเคลื่อนไหวของแผงเริ่มต้น

หลังจากทำตามขั้นตอนเหล่านี้แล้ว โค้ดของคุณควรมีลักษณะคล้ายกับโค้ดต่อไปนี้

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

ThreePaneScaffoldPredictiveBackHandler(
    navigator = scaffoldNavigator,
    backBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange
)

SupportingPaneScaffold(
    directive = scaffoldNavigator.scaffoldDirective,
    scaffoldState = scaffoldNavigator.scaffoldState,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = {
                scope.launch {
                    scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary)
                }
            }
        )
    },
    supportingPane = { SupportingPane(scaffoldNavigator = scaffoldNavigator) },
)