Xây dựng bố cục ngăn bổ trợ

Bố cục ngăn bổ trợ giúp người dùng tập trung vào nội dung chính của ứng dụng trong khi hiển thị thông tin bổ trợ có liên quan. Ví dụ: ngăn chính có thể hiển thị thông tin chi tiết về một bộ phim, trong khi ngăn phụ liệt kê các bộ phim tương tự, phim của cùng một đạo diễn hoặc tác phẩm có cùng diễn viên.

Để biết thêm thông tin chi tiết, hãy xem Nguyên tắc về ngăn hỗ trợ Material 3.

Triển khai một ngăn hỗ trợ bằng giàn giáo

NavigableSupportingPaneScaffold là một thành phần kết hợp giúp đơn giản hoá việc triển khai bố cục ngăn hỗ trợ trong Jetpack Compose. Thành phần này bao bọc SupportingPaneScaffold và thêm tính năng xử lý thao tác quay lại dự đoán và điều hướng tích hợp sẵn.

Khung ngăn hỗ trợ có thể hỗ trợ tối đa 3 ngăn:

  • Ngăn chính: Hiển thị nội dung chính.
  • Ngăn bổ trợ: Cung cấp thêm ngữ cảnh hoặc công cụ liên quan đến ngăn chính.
  • Ngăn bổ sung (không bắt buộc): Dùng cho nội dung bổ sung khi cần.

Khung hiển thị sẽ điều chỉnh dựa trên kích thước cửa sổ:

  • Trong các cửa sổ lớn, ngăn chính và ngăn bổ trợ xuất hiện cạnh nhau.
  • Trong các cửa sổ nhỏ, chỉ có một ngăn hiển thị tại một thời điểm, chuyển đổi khi người dùng di chuyển.

    Nội dung chính chiếm phần lớn màn hình, còn nội dung hỗ trợ nằm bên cạnh.
    Hình 1. Bố cục ngăn hỗ trợ.

Thêm phần phụ thuộc

NavigableSupportingPaneScaffold là một phần của thư viện bố cục thích ứng Material 3.

Thêm 3 phần phụ thuộc có liên quan sau vào tệp build.gradle của ứng dụng hoặc mô-đun:

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'
  • adaptive: Các thành phần cấp thấp như HingeInfoPosture

  • adaptive-layout: Bố cục thích ứng, chẳng hạn như ListDetailPaneScaffoldSupportingPaneScaffold

  • adaptive-navigation: Các thành phần kết hợp để di chuyển trong và giữa các ngăn, cũng như bố cục thích ứng hỗ trợ điều hướng theo mặc định, chẳng hạn như NavigableListDetailPaneScaffoldNavigableSupportingPaneScaffold

Đảm bảo dự án của bạn có compose-material3-adaptive phiên bản 1.1.0-beta1 trở lên.

Chọn sử dụng tính năng xem trước thao tác quay lại

Để bật ảnh động xem trước thao tác quay lại trong Android 15 trở xuống, bạn phải chọn sử dụng tính năng hỗ trợ thao tác xem trước thao tác quay lại. Để chọn sử dụng, hãy thêm android:enableOnBackInvokedCallback="true" vào thẻ <application> hoặc các thẻ <activity> riêng lẻ trong tệp AndroidManifest.xml.

Khi ứng dụng của bạn nhắm đến Android 16 (API cấp 36) trở lên, tính năng xem trước thao tác quay lại sẽ được bật theo mặc định.

Tạo một trình điều hướng

Trong các cửa sổ nhỏ, mỗi lần chỉ có một ngăn hiển thị, vì vậy, hãy dùng ThreePaneScaffoldNavigator để di chuyển đến và đi từ các ngăn. Tạo một thực thể của trình điều hướng bằng rememberSupportingPaneScaffoldNavigator.

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

Truyền trình điều hướng đến scaffold

Khung hiển thị này yêu cầu phải có một ThreePaneScaffoldNavigator (là một giao diện đại diện cho trạng thái của khung hiển thị), ThreePaneScaffoldValuePaneScaffoldDirective.

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

Ngăn chính và ngăn hỗ trợ là các thành phần kết hợp chứa nội dung của bạn. Sử dụng AnimatedPane để áp dụng ảnh động mặc định cho ngăn trong quá trình điều hướng. Sử dụng giá trị khung để kiểm tra xem ngăn hỗ trợ có bị ẩn hay không; nếu có, hãy hiển thị một nút gọi navigateTo(SupportingPaneScaffoldRole.Supporting) để hiển thị ngăn hỗ trợ.

Sau đây là quy trình triển khai hoàn chỉnh của khung:

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

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()) {
            Text("Supporting pane")
        }
    }
)

Trích xuất các thành phần kết hợp ngăn

Trích xuất từng ngăn của một SupportingPaneScaffold vào các thành phần kết hợp riêng để có thể sử dụng lại và kiểm thử. Sử dụng ThreePaneScaffoldScope để truy cập vào AnimatedPane nếu bạn muốn dùng ảnh động mặc định:

@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(
    modifier: Modifier = Modifier,
) {
    AnimatedPane(modifier = modifier.safeContentPadding()) {
        // Supporting pane content
        Text("This is the supporting pane")
    }
}

Việc trích xuất các ngăn vào thành phần kết hợp giúp đơn giản hoá việc sử dụng SupportingPaneScaffold (hãy so sánh phần sau với quá trình triển khai hoàn chỉnh của khung ở phần trước):

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

Nếu bạn cần kiểm soát chặt chẽ hơn các khía cạnh cụ thể của khung, hãy cân nhắc sử dụng SupportingPaneScaffold thay vì NavigableSupportingPaneScaffold. Thao tác này chấp nhận PaneScaffoldDirectiveThreePaneScaffoldValue hoặc ThreePaneScaffoldState riêng biệt. Tính linh hoạt này cho phép bạn triển khai logic tuỳ chỉnh cho khoảng cách giữa các ngăn và xác định số lượng ngăn cần hiển thị đồng thời. Bạn cũng có thể bật tính năng hỗ trợ cử chỉ xem trước thao tác quay lại bằng cách thêm ThreePaneScaffoldPredictiveBackHandler.

Thêm ThreePaneScaffoldPredictiveBackHandler

Đính kèm trình xử lý xem trước thao tác quay lại lấy một phiên bản trình điều hướng giàn giáo và chỉ định backBehavior. Điều này xác định cách các đích đến được đẩy ra khỏi ngăn xếp lui trong quá trình điều hướng quay lại. Sau đó, hãy truyền scaffoldDirectivescaffoldState đến SupportingPaneScaffold. Sử dụng phương thức nạp chồng chấp nhận ThreePaneScaffoldState, truyền vào scaffoldNavigator.scaffoldState.

Xác định ngăn chính và ngăn phụ trong SupportingPaneScaffold. Sử dụng AnimatedPane cho ảnh động mặc định của ngăn.

Sau khi bạn triển khai các bước này, mã của bạn sẽ trông giống như sau:

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