Utwórz adaptacyjną nawigację

Większość aplikacji ma kilka miejsc docelowych najwyższego poziomu, do których można uzyskać dostęp za pomocą głównego interfejsu nawigacji aplikacji. W kompaktowych oknach, np. na standardowym ekranie telefonu, miejsca docelowe są zwykle wyświetlane na pasku nawigacyjnym u dołu okna. W przypadku rozszerzonego okna, np. aplikacji na tablecie działającej w trybie pełnoekranowym, lepszym rozwiązaniem jest zwykle pasek nawigacyjny obok aplikacji, ponieważ elementy sterujące nawigacją są łatwiej dostępne podczas trzymania urządzenia po lewej i prawej stronie.

NavigationSuiteScaffold ułatwia przełączanie się między interfejsami nawigacji, wyświetlając odpowiedni komponent interfejsu nawigacji na podstawie wartości WindowSizeClass. Obejmuje to dynamiczną zmianę interfejsu podczas zmiany rozmiaru okna w czasie działania. Domyślnie wyświetlany jest jeden z tych komponentów interfejsu:

  • Pasek nawigacyjny, jeśli szerokość lub wysokość jest niewielka lub urządzenie jest w pozycji stołowej.
  • Kolumna nawigacji w przypadku wszystkich pozostałych
Rysunek 1. NavigationSuiteScaffold wyświetla pasek nawigacyjny w kompaktowych oknach.
Rysunek 2. NavigationSuiteScaffold wyświetla kolumnę nawigacji w rozwiniętych oknach.

Dodawanie zależności

NavigationSuiteScaffold jest częścią biblioteki Material3 adaptive navigation suite. Dodaj zależność z biblioteką w pliku build.gradle aplikacji lub modułu:

Kotlin

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

Groovy

implementation 'androidx.compose.material3:material3-adaptive-navigation-suite'

Tworzenie szkieletu

NavigationSuiteScaffold składa się z 2 głównych części: elementów pakietu nawigacyjnego i treści wybranego miejsca docelowego. Elementy pakietu nawigacyjnego możesz zdefiniować bezpośrednio w komponowalnym elemencie, ale często są one definiowane w innych miejscach, np. w wyliczeniu:

enum class AppDestinations(
    @StringRes val label: Int,
    val icon: ImageVector,
    @StringRes val contentDescription: Int
) {
    HOME(R.string.home, Icons.Default.Home, R.string.home),
    FAVORITES(R.string.favorites, Icons.Default.Favorite, R.string.favorites),
    SHOPPING(R.string.shopping, Icons.Default.ShoppingCart, R.string.shopping),
    PROFILE(R.string.profile, Icons.Default.AccountBox, R.string.profile),
}

Aby korzystać z NavigationSuiteScaffold, musisz śledzić bieżące miejsce docelowe. Możesz to zrobić za pomocą rememberSaveable:

var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) }

W przykładzie poniżej parametr navigationSuiteItems (type NavigationSuiteScope) używa funkcji item do zdefiniowania interfejsu nawigacji dla poszczególnych miejsc docelowych. Interfejs miejsca docelowego jest używany na paskach nawigacyjnych, w panelach i szufladach. Aby utworzyć elementy nawigacyjne, przejdź do pętli AppDestinations (zdefiniowanej we wcześniejszym fragmencie kodu):

NavigationSuiteScaffold(
    navigationSuiteItems = {
        AppDestinations.entries.forEach {
            item(
                icon = {
                    Icon(
                        it.icon,
                        contentDescription = stringResource(it.contentDescription)
                    )
                },
                label = { Text(stringResource(it.label)) },
                selected = it == currentDestination,
                onClick = { currentDestination = it }
            )
        }
    }
) {
    // TODO: Destination content.
}

W funkcji lambda treści docelowych użyj wartości currentDestination, aby określić, który interfejs użytkownika ma być wyświetlany. Jeśli w aplikacji używasz biblioteki nawigacyjnej, użyj jej tutaj, aby wyświetlić odpowiednie miejsce docelowe. Wystarczy instrukcja when:

NavigationSuiteScaffold(
    navigationSuiteItems = { /*...*/ }
) {
    // Destination content.
    when (currentDestination) {
        AppDestinations.HOME -> HomeDestination()
        AppDestinations.FAVORITES -> FavoritesDestination()
        AppDestinations.SHOPPING -> ShoppingDestination()
        AppDestinations.PROFILE -> ProfileDestination()
    }
}

Zmień kolory

NavigationSuiteScaffold tworzy Surface na całym obszarze, który zajmuje struktura, zwykle w całym oknie. Dodatkowo szkielet rysuje konkretny interfejs nawigacji, np. NavigationBar. Zarówno powierzchnia, jak i interfejs nawigacji korzystają z wartości określonych w motywie aplikacji, ale możesz je zastąpić.

Parametr containerColor określa kolor powierzchni. Domyślnie jest to kolor tła schematu kolorów. Parametr contentColor określa kolor treści na tej platformie. Domyślnie jest to kolor „włączony” określony dla containerColor. Jeśli na przykład containerColor używa koloru background, to contentColor używa koloru onBackground. Więcej informacji o działaniu systemu kolorów znajdziesz w artykule Tworzenie motywów Material Design 3 w Compose. Podczas zastępowania tych wartości używaj wartości zdefiniowanych w motywie, aby aplikacja obsługiwała tryby wyświetlania jasny i ciemny:

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    containerColor = MaterialTheme.colorScheme.primary,
    contentColor = MaterialTheme.colorScheme.onPrimary,
) {
    // Content...
}

Interfejs nawigacji jest rysowany przed powierzchnią NavigationSuiteScaffold. Domyślne wartości kolorów interfejsu są podawane przez NavigationSuiteDefaults.colors(), ale możesz je zastąpić. Jeśli na przykład chcesz, aby tło paska nawigacyjnego było przezroczyste, ale inne wartości były domyślne, zastąp navigationBarContainerColor:

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    navigationSuiteColors = NavigationSuiteDefaults.colors(
        navigationBarContainerColor = Color.Transparent,
    )
) {
    // Content...
}

Ostatecznie możesz dostosować każdy element w interfejsie nawigacji. Podczas wywoływania funkcji item możesz przekazać instancję NavigationSuiteItemColors. Klasa określa kolory elementów na pasku nawigacyjnym, w panelu nawigacyjnym i w szufladzie nawigacyjnej. Oznacza to, że możesz mieć identyczne kolory w przypadku każdego typu interfejsu nawigacyjnego lub możesz je zmieniać w zależności od potrzeb. Zdefiniuj kolory na poziomie NavigationSuiteScaffold, aby używać tego samego wystąpienia obiektu dla wszystkich elementów, i wywołaj funkcję NavigationSuiteDefaults.itemColors(), aby zastąpić tylko te elementy, które chcesz zmienić:

val myNavigationSuiteItemColors = NavigationSuiteDefaults.itemColors(
    navigationBarItemColors = NavigationBarItemDefaults.colors(
        indicatorColor = MaterialTheme.colorScheme.primaryContainer,
        selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer
    ),
)

NavigationSuiteScaffold(
    navigationSuiteItems = {
        AppDestinations.entries.forEach {
            item(
                icon = {
                    Icon(
                        it.icon,
                        contentDescription = stringResource(it.contentDescription)
                    )
                },
                label = { Text(stringResource(it.label)) },
                selected = it == currentDestination,
                onClick = { currentDestination = it },
                colors = myNavigationSuiteItemColors,
            )
        }
    },
) {
    // Content...
}

Dostosowywanie typów nawigacji

Domyślne działanie NavigationSuiteScaffold zmienia interfejs nawigacji na podstawie klas rozmiaru okna. Możesz jednak zastąpić to działanie. Jeśli na przykład aplikacja wyświetla pojedynczy duży panel treści w przypadku pliku danych, może używać stałego panelu nawigacyjnego w przypadku rozszerzonych okien, ale nadal wracać do domyślnego działania w przypadku kompaktowych i średnich klas rozmiaru okna:

val adaptiveInfo = currentWindowAdaptiveInfo()
val customNavSuiteType = with(adaptiveInfo) {
    if (windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND)) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
    }
}

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    layoutType = customNavSuiteType,
) {
    // Content...
}

Dodatkowe materiały