Разработка
Обновления в адаптивных макетах Compose с I/O
Эти новые функции позволяют нам создавать действительно отзывчивые и удобные приложения для Android.
В этом году на Google I/O было сделано множество интересных анонсов, причем не только в области искусственного интеллекта (хотя и это, безусловно, было важным моментом). Для меня ключевым моментом стало внимание к усовершенствованиям в Jetpack Compose для создания адаптивных макетов. Поскольку Android выходит за рамки смартфонов, переходя на планшеты, раскладушки и большие экраны, создание приложений, адаптирующихся к различным форм-факторам, становится как никогда актуальным.
В своей предыдущей статье я уже рассказывал об отзывчивых макетах с использованием Window Size классов. Однако новые интересные разработки в Jetpack Compose побудили меня вернуться к этой теме. Появилась не только новая реализация WindowSizeClass, упрощающая его использование, но и новые composable функции, которые упрощают общее поведение макета, устраняя необходимость в кастомных функциях.
Поскольку нам предстоит проделать большую работу, давайте сразу перейдем к первому ключевому аспекту: новой реализации WindowSizeClass. Чтобы обеспечить контекст, я начну с переноса реализации из моей предыдущей статьи, посмотрите на нее, если хотите.
Миграция WindowSizeClass
Начнем с обновления нашего Gradle-файла. Мы удалим старую зависимость и добавим новую для обновленной реализации.
[versions] adaptive = "1.0.0-beta01" ... [libraries] -- androidx-material3-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class" } androidx-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "adaptive" } ...
Обновив зависимость Gradle, давайте изменим участки кода, которые ранее полагались на старую реализацию WindowSizeClass
. Например, если у вас был код, который динамически определял количество колонок в зависимости от размера окна, мы можем обновить его следующим образом:
val windowWidthSize = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass val columns = when (windowWidthSize) { WindowWidthSizeClass.COMPACT -> 1 WindowWidthSizeClass.MEDIUM -> 2 else -> 3 }
Как видите, обновление не ограничилось только изменением функций! Ключевым улучшением является возможность получения класса windowSizeClass непосредственно в любой композитной функции, что избавляет от необходимости обращаться к нему через Активити. Это означает, что вы наконец-то сможете отказаться от практики передачи класса размера окна в качестве параметра во всем приложении! Это значительный шаг вперед для более чистого кода.
NavigationSuiteScaffold
С миграцией windowSizeClass мы разобрались, давайте перейдем к новым интересным composable функциям. Во-первых, это NavigationSuiteScaffold
. Эта важная композабл функция устраняет необходимость в кастомной логике при переключении между нижними панелями навигации, навигационными рельсами и выдвигающимися меню в зависимости от размера окна.
В предыдущей статье я рассмотрел создание кастомного решения для переключения элементов навигации. Теперь давайте посмотрим, как NavigationSuiteScaffold
упрощает этот процесс. Вот как вы можете использовать эту новую функцию для достижения того же результата:
... NavigationSuiteScaffold( modifier = Modifier, navigationSuiteItems = { bottomNavigationItems.forEach { bottomBarElement -> val selected = currentScreen.instanceOf(bottomBarElement.screen::class) item( icon = bottomBarElement.icon, selected = selected, alwaysShowLabel = true, label = { Text( text = stringResource(id = bottomBarElement.title), style = MaterialTheme.typography.labelMedium.copy( textAlign = TextAlign.Center, fontWeight = FontWeight.Normal ), maxLines = 1 ) }, onClick = { if (!selected) { NavigationEvent.OnNavigateBottomBar( bottomBarElement.screen ) } } ) } } ) { Scaffold() { innerPadding -> MainNavHost( modifier = Modifier.padding(innerPadding), ) } }
Эта единая реализация автоматически адаптирует свое поведение для обеспечения подходящей навигации в зависимости от текущего размера окна. Она обеспечивает соответствующую навигацию, переключаясь между такими элементами, как нижняя панель на маленьких экранах и навигационный рельс на больших экранах.
Однако для тех, кто предпочитает более индивидуальный подход, NavigationSuiteScaffold
предлагает гибкость с помощью NavigationSuiteType
. Вы можете легко интегрировать пользовательское поведение в функцию, что позволит вам использовать такие элементы, как навигационный рельс, даже в ландшафтном режиме на смартфонах.
... val adaptiveInfo = currentWindowAdaptiveInfo() val customNavSuiteType = with(adaptiveInfo) { if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED) { NavigationSuiteType.NavigationRail } else { NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo) } } NavigationSuiteScaffold( modifier = Modifier, layoutType = customNavSuiteType, navigationSuiteItems = { ...
Тот же принцип действует, если вы хотите полностью скрыть навигацию в определенных сценариях.
... val adaptiveInfo = currentWindowAdaptiveInfo() val customNavSuiteType = with(adaptiveInfo) { when { !shouldShowBottomBar -> { NavigationSuiteType.None } windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED -> { NavigationSuiteType.NavigationRail } else -> { NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo) } } } ...
Результат будет примерно таким:
ListDetailPaneScaffold
Хорошо, давайте рассмотрим еще одну мощную функцию — ListDetailPaneScaffold
. Эта функция идеально подходит для сценариев, в которых вы хотите отобразить два экрана (или панели, как следует из названия) бок о бок на больших размерах окна. Одним из ее ключевых преимуществ является автоматическая обработка внутренней навигации, независимо от того, выбираете ли вы отображение одной панели или используете двухпанельный макет. Это не только упрощает разработку, но и обеспечивает плавное взаимодействие с пользователем независимо от размера окна.
[versions] material3AdaptiveNavigationSuiteAndroid = "1.3.0-beta01" ... [libraries] androidx-material3-adaptive-navigation-suite-android = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite-android", version.ref = "material3AdaptiveNavigationSuiteAndroid" } ...
Установив зависимость в Gradle, мы можем приступить к обновлению кода.
val navigator = rememberListDetailPaneScaffoldNavigator<String>() BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() } ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, listPane = { AnimatedPane { HomeScreen( onClickOnItem = { navigator.navigateTo( ListDetailPaneScaffoldRole.Detail, it ) } ) } }, detailPane = { AnimatedPane { navigator.currentDestination?.content?.let { ZoomBookInitScreen(book = it.id) } } }, )
Как следует из названия, навигатор отвечает за управление навигацией внутри панелей. Сюда входит переход к панели деталей, а также обратная навигация в однопанельном режиме. Он также включает объект данных, переданный в панель деталей. Примечательно, что вы можете передавать даже пользовательские объекты, если они являются Parcelable.
Очевидно, что мы можем использовать ListDetailPaneScaffold
с WindowSizeClass
, чтобы настроить поведение в зависимости от текущего размера окна. Например (как вы видите на скриншотах), вы можете условно отображать такие элементы, как стрелка назад, только в однопанельном режиме.
val windowWidthSize = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass val backVisible = when (windowWidthSize) { WindowWidthSizeClass.EXPANDED -> false else -> true }
Такой уровень контроля обеспечивает превосходный пользовательский опыт на устройствах разного размера.
SupportingPaneScaffold
Чтобы завершить знакомство с новыми функциями адаптивной верстки, давайте вкратце обсудим SupportingPaneScaffold
. Этот композит имеет сходство с ListDetailPaneScaffold
в своей основной функциональности: управление навигацией и отображение содержимого в панелях. Однако SupportingPaneScaffold
предназначен для сценариев, в которых у вас есть основная панель с контентом и меньшая, «вспомогательная» панель справа. Это делает ее идеальной для ситуаций, когда вторичный контент предоставляет дополнительную информацию или дополняет основной контент, но не требует равного пространства на экране.
Если вы хотите реализовать SupportingPaneScaffold
в своем проекте, вы можете использовать знания, полученные в ListDetailPaneScaffold
, в качестве основы. Для более глубокого погружения я оставляю здесь официальную документацию Jetpack Compose.
Выводы
Как вы убедились из этой статьи, конференция Google I/O 2024 принесла интересные новинки в создании адаптивных макетов с помощью Jetpack Compose. Новая реализация WindowSizeClass
упрощает доступ и использование, а такие мощные композитные функции, как NavigationSuiteScaffold
, ListDetailPaneScaffold
и SupportingPaneScaffold
, предлагают оптимизированный подход к навигации и представлению контента на экранах различных размеров и форм-факторов.
Эти новые функции позволяют нам создавать действительно отзывчивые и удобные приложения для Android. Используя эти инструменты и следуя лучшим практикам адаптивных макетов, вы сможете обеспечить изящную адаптацию ваших приложений к развивающейся экосистеме Android, предоставляя исключительный опыт пользователям на всех устройствах.