Membangun tata letak panel pendukung

Tata letak panel pendukung menjaga fokus pengguna pada konten utama aplikasi sekaligus menampilkan informasi pendukung yang relevan. Misalnya, panel utama dapat menampilkan detail tentang film, sedangkan panel pendukung mencantumkan film serupa, film dari sutradara yang sama, atau karya yang menampilkan aktor yang sama.

Untuk mengetahui detail selengkapnya, lihat panduan panel pendukung Material 3.

Mengimplementasikan panel pendukung dengan scaffold

NavigableSupportingPaneScaffold adalah composable yang menyederhanakan implementasi tata letak panel pendukung di Jetpack Compose. Composable ini menggabungkan SupportingPaneScaffold dan menambahkan penanganan navigasi dan kembali prediktif bawaan handling.

Scaffold panel pendukung mendukung hingga tiga panel:

  • Panel utama: Menampilkan konten utama.
  • Panel pendukung: Menyediakan konteks atau alat tambahan yang terkait dengan panel utama.
  • Panel tambahan (opsional): Digunakan untuk konten tambahan jika diperlukan.

Scaffold menyesuaikan berdasarkan ukuran jendela:

  • Di jendela besar, panel utama dan panel pendukung muncul berdampingan.
  • Di jendela kecil, hanya satu panel yang terlihat dalam satu waktu, dan akan beralih saat pengguna melakukan navigasi.

    Konten utama menempati sebagian besar tampilan dengan konten pendukung di sampingnya.
    Gambar 1. Tata letak panel pendukung.

Menambahkan dependensi

NavigableSupportingPaneScaffold adalah bagian dari library tata letak adaptif Material 3.

Tambahkan tiga dependensi terkait berikut ke file build.gradle aplikasi atau modul Anda:

Kotlin

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

Groovy

implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
  • adaptif: Elemen penyusun tingkat rendah seperti HingeInfo dan Posture

  • adaptive-layout: Tata letak adaptif seperti ListDetailPaneScaffold dan SupportingPaneScaffold

  • adaptive-navigation: Composable untuk navigasi di dalam dan antar panel, serta tata letak adaptif yang mendukung navigasi secara default seperti NavigableListDetailPaneScaffold dan NavigableSupportingPaneScaffold

Pastikan project Anda menyertakan compose-material3-adaptive versi 1.1.0-beta1 atau yang lebih tinggi.

Memilih untuk menggunakan gestur kembali prediktif

Untuk mengaktifkan animasi kembali prediktif di Android 15 atau yang lebih rendah, Anda harus memilih untuk mendukung gestur kembali prediktif. Untuk memilih, tambahkan android:enableOnBackInvokedCallback="true" ke tag <application> atau tag <activity> individual dalam file AndroidManifest.xml.

Setelah aplikasi Anda menargetkan Android 16 (level API 36) atau yang lebih tinggi, kembali prediktif akan diaktifkan secara default.

Membuat navigator

Di jendela kecil, hanya satu panel yang ditampilkan dalam satu waktu, jadi gunakan ThreePaneScaffoldNavigator untuk berpindah ke dan dari panel. Buat instance dari navigator dengan rememberSupportingPaneScaffoldNavigator.

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

Meneruskan navigator ke scaffold

Scaffold memerlukan ThreePaneScaffoldNavigator yang merupakan antarmuka yang mewakili status scaffold, ThreePaneScaffoldValue dan PaneScaffoldDirective.

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

Panel utama dan panel pendukung adalah composable yang berisi konten Anda. Gunakan AnimatedPane untuk menerapkan animasi panel default selama navigasi. Gunakan nilai scaffold untuk memeriksa apakah panel pendukung disembunyikan; jika ya, tampilkan tombol yang memanggil navigateTo(SupportingPaneScaffoldRole.Supporting) untuk menampilkan panel pendukung.

Untuk layar besar, gunakan ThreePaneScaffoldNavigator.navigateBack() metode untuk menutup panel pendukung, dengan meneruskan BackNavigationBehavior.PopUntilScaffoldValueChange konstanta. Memanggil metode ini akan memaksa rekomposisi dari NavigableSupportingPaneScaffold. Selama rekomposisi, periksa properti ThreePaneScaffoldNavigator.currentDestination untuk menentukan apakah akan menampilkan panel pendukung.

Berikut adalah implementasi lengkap 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")
            }

        }
    }
)

Mengekstrak composable panel

Ekstrak setiap panel SupportingPaneScaffold ke dalam composable-nya sendiri agar dapat digunakan kembali dan diuji. Gunakan ThreePaneScaffoldScope untuk mengakses AnimatedPane jika Anda menginginkan animasi default:

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

    }
}

Mengekstrak panel ke dalam composable akan menyederhanakan penggunaan SupportingPaneScaffold (bandingkan hal berikut dengan implementasi lengkap scaffold di bagian sebelumnya):

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

Jika Anda memerlukan kontrol lebih besar atas aspek tertentu dari scaffold, sebaiknya gunakan SupportingPaneScaffold dan bukan NavigableSupportingPaneScaffold. Hal ini menerima PaneScaffoldDirective dan ThreePaneScaffoldValue atau ThreePaneScaffoldState secara terpisah. Fleksibilitas ini memungkinkan Anda menerapkan logika kustom untuk penspasian panel dan menentukan jumlah panel yang harus ditampilkan secara bersamaan. Anda juga dapat mengaktifkan dukungan kembali prediktif dengan menambahkan ThreePaneScaffoldPredictiveBackHandler.

Menambahkan ThreePaneScaffoldPredictiveBackHandler

Lampirkan pengendali kembali prediktif yang mengambil instance navigator scaffold dan tentukan backBehavior. Hal ini menentukan cara tujuan dikeluarkan dari data sebelumnya selama navigasi kembali. Kemudian, teruskan scaffoldDirective dan scaffoldState ke SupportingPaneScaffold. Gunakan overload yang menerima ThreePaneScaffoldState, dengan meneruskan scaffoldNavigator.scaffoldState.

Tentukan panel utama dan panel pendukung dalam SupportingPaneScaffold. Gunakan AnimatedPane untuk animasi panel default.

Setelah menerapkan langkah-langkah ini, kode Anda akan terlihat seperti berikut:

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