Site icon AppTractor

Декларативные адаптивные макеты: использование метаданных Nav3 для многопанельного UI

В мире Android-разработки «адаптивная вёрстка» (Adaptive Layout) перестала быть роскошью — теперь это необходимость. С появлением складных устройств и девайсов с большими экранами приложениям уже недостаточно просто растягиваться — им нужно перестраивать свою структуру UI.

Традиционно реализация сценариев вроде List-Detail или Supporting Pane требовала большого количества бойлерплейта: проверки классов window size, ручное управление back stack и обработка условной логики интерфейса. Nav3 — современный подход к навигации от Google — предлагает новый способ моделирования адаптивного UI через метаданные навигации.

Настройка: адаптивность «из коробки»

Основой современного адаптивного приложения является NavigationSuiteScaffold. Он автоматически переключается между BottomNavigationBar на компактных экранах и NavigationRail на более широких.

В своём проекте я использовал стандартные navigationState и реализацию Navigator, как рекомендуется в документации Google, чтобы сохранить основную логику чистой и предсказуемой.

val navigationState = rememberNavigationState(
    startKey = HomeKey,
    topLevelKeys = setOf(HomeKey, SearchKey, ProfileKey),
)
val navigator = remember(navigationState) { Navigator(navigationState) }

NavigationSuiteScaffold(
    navigationSuiteItems = {
        TopDestination.entries.forEach { dest ->
            item(
                selected = navigationState.currentTopLevelKey == dest.key,
                onClick = { navigator.navigate(dest.key) },
                icon = { Icon(dest.icon, contentDescription = dest.label) },
                label = { Text(dest.label) },
            )
        }
    }
) {
    // Scaffold and NavDisplay go here
}

Перед тем как переходить к панелям, важно разобраться с контейнером верхнего уровня навигации. В этом проекте я использовал NavigationSuiteScaffold, который даёт готовое «из коробки» решение для адаптивной навигации.

Вместо того чтобы вручную проверять размеры экрана, scaffold сам использует текущую информацию об адаптивности окна (window adaptive info) и переключается между BottomNavigationBar для компактных экранов и NavigationRail для расширенных макетов (например, планшетов или разложенных foldable-устройств). Это гарантирует, что основная навигация остаётся удобной и доступной вне зависимости от того, как пользователь держит или разворачивает устройство.

Ядро: навигационные метаданные как драйвер layout’а

Самая мощная фича Nav3 от Google — возможность прикреплять метаданные к элементу навигации (navigation entry). Вместо того чтобы хардкодить условия вроде «если экран широкий — показываем detail», мы просто указываем, какой «pane» представляет данный экран.

Стоит учитывать, что эти API всё ещё развиваются и могут меняться по мере развития Nav3.

1. Симфония из трёх панелей: List, Detail и Extra

Большинство адаптивных приложений ограничиваются паттерном List-Detail. Однако современные сценарии многозадачности часто требуют третьего уровня — Extra Pane.

Именно здесь метаданные Nav3 раскрываются на полную: определяя listPane(), detailPane() и extraPane(), мы создаём иерархию экранов, которую навигационная система может гибко оркестрировать в зависимости от доступного пространства на экране.

entry<ListKey>(
    metadata = listPane(detailPlaceholder = {
        Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text(
                "Select an item",
                style = MaterialTheme.typography.headlineMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
        }
    })
) {
    ListScreen(
        onItemClick = { navigator.navigate(DetailKey(it)) },
        onBack = { navigator.goBack() },
    )
}
entry<DetailKey>(metadata = detailPane()) { key ->
    DetailScreen(
        key.itemId,
        onBack = { navigator.goBack() },
        onOpenExtra = { navigator.navigate(ExtraInfoKey(key.itemId)) },
    )
}
entry<ExtraInfoKey>(metadata = extraPane()) { key ->
    ExtraInfoScreen(key.itemId, onBack = { navigator.goBack() })
}

Как это выглядит на практике:

Такое поведение гарантирует, что наиболее релевантный контент (элемент, который вы просматриваете, и его дополнительные детали) всегда находится в центре внимания, при этом навигация остаётся плавной и логически связанной через back stack в Nav3.

2. За пределами стандартных паттернов: Supporting Pane для медиа

Адаптивный дизайн — это не только про списки. Иногда нужен Supporting Pane — например, комментарии под видео или метаданные рядом с плеером. Nav3 позволяет комбинировать разные стратегии в рамках одного приложения.

entry<VideoKey>(metadata = mainPane() + preferredPaneSize(height = 0.4f)) {
    VideoScreen(
        onOpenComments = { navigator.navigate(CommentsKey) },
        onBack = { navigator.goBack() },
    )
}
entry<CommentsKey>(metadata = supportingPane()) {
    CommentsScreen(onBack = { navigator.goBack() })
}

Комбинируя mainPane() с preferredPaneSize, мы даём layout-движку подсказку, как сбалансировать пространство на экране. Когда CommentsKey попадает в стек, метаданные supportingPane() сигнализируют системе, что его нужно показать рядом с видео, а не перекрывать его.

Как это выглядит на практике:

Используя preferredPaneSize(height = 0.4f), вы обеспечиваете сохранение пропорций и видимости видео, в то время как вспомогательный контент адаптируется под доступное пространство.

3. Оркестрация UI с помощью Scene Strategies

Все эти метаданные были бы бесполезны без механизма, который их интерпретирует. Здесь в игру вступают NavDisplay и Scene Strategies.

В NavDisplay мы комбинируем разные стратегии, чтобы одновременно обрабатывать как сценарии List-Detail, так и поведение Supporting Pane.

NavDisplay(
    entries = navigationState.toEntries(mainEntryProvider),
    modifier = Modifier.padding(innerPadding),
    sceneStrategy = rememberListDetailSceneStrategy<NavKey>() then
            rememberSupportingPaneSceneStrategy(shouldHandleSinglePaneLayout = true),
    onBack = { navigator.goBack() },
)

Оператор then позволяет нам цепочкой объединять стратегии. NavDisplay анализирует верхние элементы вашего навигационного стека, читает их метаданные (listPane, detailPane, supportingPane) и передаёт управление стратегиям, которые решают, как в итоге отрендерить адаптивный UI.

Преимущества подхода Nav3

В завершение стоит подчеркнуть, почему декларативный подход лучше того, что было раньше:

Заключение

Navigation 3 — это серьёзный шаг в сторону по-настоящему декларативной навигации в Jetpack Compose от Google. Перенося ответственность за оркестрацию макета с UI-экранов на навигационные метаданные, мы получаем гораздо более чистое разделение ответственности.

Теперь экраны не заботятся о том, отображаются ли они в single-pane или multi-pane окружении — они просто рендерят свой контент, а Nav3 отвечает за «как» и «где» его показать.

Рассмотренные паттерны — от «скользящего» трёхпанельного интерфейса до адаптивных supporting panes — показывают, что Nav3 — это не просто про навигацию между экранами, а полноценный фреймворк для создания современных приложений, нативно чувствующих себя на любом форм-факторе.

Исходный код

Если хотите посмотреть, как Navigation 3 и Material 3 Adaptive работают вместе в реальном проекте, изучите пример на GitHub.

Рекомендую склонировать репозиторий, запустить его на эмуляторе foldable-устройства и поэкспериментировать, добавляя собственные layout’ы на основе метаданных.

Источник

Exit mobile version