Поддержка различных размеров дисплеев обеспечивает доступ к вашему приложению с самых разных устройств и для наибольшего количества пользователей.
Чтобы поддерживать как можно больше размеров дисплеев — будь то разные экраны устройств или разные окна приложений в многооконном режиме — спроектируйте макеты приложений так, чтобы они были отзывчивыми и адаптивными. Отзывчивые/адаптивные макеты обеспечивают оптимизированный пользовательский опыт независимо от размера дисплея, позволяя вашему приложению адаптироваться к телефонам, планшетам, складным устройствам, устройствам ChromeOS, портретной и альбомной ориентациям, а также изменяемым конфигурациям дисплеев, таким как режим разделения экрана и работа с окнами рабочего стола.
Адаптивные макеты изменяются в зависимости от доступного пространства дисплея. Изменения варьируются от небольших корректировок макета, которые заполняют пространство (адаптивный дизайн), до полной замены одного макета другим, чтобы ваше приложение могло лучше подстраиваться под разные размеры дисплеев (адаптивный дизайн).
Jetpack Compose — это декларативный набор инструментов пользовательского интерфейса, который идеально подходит для проектирования и реализации макетов, которые динамически изменяются для разного отображения контента на дисплеях разных размеров.
Вносите явные изменения в макет для компонуемых элементов на уровне контента
Компонуемые элементы уровня приложения и контента занимают все доступное вашему приложению пространство на дисплее. Для таких компонуемых элементов может иметь смысл изменить общую компоновку вашего приложения на больших дисплеях.
Избегайте использования физических значений оборудования для принятия решений о компоновке. Может возникнуть соблазн принимать решения на основе фиксированного осязаемого значения (Является ли устройство планшетом? Имеет ли физический экран определенное соотношение сторон?), но ответы на эти вопросы могут оказаться бесполезными для определения пространства, доступного для вашего пользовательского интерфейса.

На планшетах приложение может работать в многооконном режиме, что означает, что приложение может разделять экран с другим приложением. В режиме окон рабочего стола или в ChromeOS приложение может находиться в окне с изменяемым размером. Может быть даже более одного физического экрана, например, в складном устройстве. Во всех этих случаях физический размер экрана не имеет значения для принятия решения о том, как отображать контент.
Вместо этого вам следует принимать решения на основе фактической части экрана, выделенной для вашего приложения, описанной текущими метриками окна, предоставленными библиотекой Jetpack WindowManager . Пример использования WindowManager в приложении Compose см. в примере JetNews .
Адаптация макетов к доступному пространству дисплея также сокращает объем специальной обработки, необходимой для поддержки таких платформ, как ChromeOS, и таких форм-факторов, как планшеты и складные устройства.
Когда вы определили метрики пространства, доступного для вашего приложения, преобразуйте исходный размер в класс размера окна, как описано в разделе Использование классов размера окна . Классы размера окна — это контрольные точки, разработанные для баланса простоты логики приложения с гибкостью оптимизации вашего приложения для большинства размеров дисплея. Классы размера окна относятся к общему окну вашего приложения, поэтому используйте классы для решений по макету, которые влияют на общий макет вашего приложения. Вы можете передать классы размера окна вниз как состояние или выполнить дополнительную логику для создания производного состояния для передачи вниз вложенным компонуемым объектам.
@Composable fun MyApp( windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass ) { // Decide whether to show the top app bar based on window size class. val showTopAppBar = windowSizeClass.isHeightAtLeastBreakpoint(WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND) // MyScreen logic is based on the showTopAppBar boolean flag. MyScreen( showTopAppBar = showTopAppBar, /* ... */ ) }
Многоуровневый подход ограничивает логику размера дисплея одним местом, а не разбрасывает ее по всему приложению во многих местах, которые необходимо синхронизировать. Единое место создает состояние, которое может быть явно передано другим компонуемым объектам, как и любое другое состояние приложения. Явная передача состояния упрощает отдельные компонуемые объекты, поскольку компонуемые объекты принимают класс размера окна или указанную конфигурацию вместе с другими данными.
Гибкие вложенные компонуемые объекты можно использовать повторно
Composables более пригодны для повторного использования, когда их можно разместить в самых разных местах. Если composable должен быть помещен в определенное место с определенным размером, то вряд ли его можно будет повторно использовать в других контекстах. Это также означает, что отдельные, повторно используемые composables должны избегать неявной зависимости от глобальной информации о размере дисплея.
Представьте себе вложенный компонуемый объект, реализующий макет списка-детали , который может отображать либо одну панель, либо две панели рядом:

Решение о детализации списка должно быть частью общей компоновки приложения, поэтому решение передается из компонуемого элемента на уровне контента:
@Composable fun AdaptivePane( showOnePane: Boolean, /* ... */ ) { if (showOnePane) { OnePane(/* ... */) } else { TwoPane(/* ... */) } }
Что, если вместо этого вы хотите, чтобы компонуемый элемент независимо менял свой макет в зависимости от доступного пространства дисплея, например, карточка, которая показывает дополнительные сведения, если позволяет пространство? Вы хотите выполнить некоторую логику на основе некоторого доступного размера дисплея, но какого именно размера?

Не пытайтесь использовать размер фактического экрана устройства. Это не будет точным для разных типов экранов, а также не будет точным, если приложение не полноэкранное.
Поскольку компонуемый элемент не является компонуемым элементом уровня содержимого, не используйте текущие метрики окна напрямую. Если компонент размещен с отступами (например, с вставками) или если приложение включает такие компоненты, как навигационные рельсы или панели приложений, объем доступного для компонуемого элемента пространства отображения может значительно отличаться от общего пространства, доступного для приложения.
Используйте ширину, которую компонуемый элемент фактически дает для рендеринга самого себя. У вас есть два варианта получить эту ширину:
Если вы хотите изменить, где или как отображается контент, используйте набор модификаторов или пользовательский макет, чтобы сделать макет адаптивным. Это может быть так же просто, как заполнение дочерним элементом всего доступного пространства или размещение дочерних элементов с несколькими столбцами, если места достаточно.
Если вы хотите изменить то, что вы показываете, используйте
BoxWithConstraints
как более мощную альтернативу.BoxWithConstraints
предоставляет ограничения измерений , которые вы можете использовать для вызова различных компонуемых объектов на основе доступного пространства дисплея. Однако это требует некоторых затрат, посколькуBoxWithConstraints
откладывает компоновку до фазы макета, когда эти ограничения известны, что приводит к выполнению большего объема работы во время макета.
@Composable fun Card(/* ... */) { BoxWithConstraints { if (maxWidth < 400.dp) { Column { Image(/* ... */) Title(/* ... */) } } else { Row { Column { Title(/* ... */) Description(/* ... */) } Image(/* ... */) } } } }
Убедитесь, что все данные доступны для разных размеров дисплеев.
При реализации компонуемого объекта, использующего дополнительное пространство на дисплее, у вас может возникнуть соблазн проявить эффективность и загрузить данные как побочный эффект текущего размера дисплея.
Однако это противоречит принципу однонаправленного потока данных, где данные могут быть подняты и предоставлены компонуемым для надлежащего отображения. Достаточно данных должно быть предоставлено компонуемому, чтобы компонуемый всегда имел достаточно контента для любого размера дисплея, даже если некоторая часть контента может не всегда использоваться.
@Composable fun Card( imageUrl: String, title: String, description: String ) { BoxWithConstraints { if (maxWidth < 400.dp) { Column { Image(imageUrl) Title(title) } } else { Row { Column { Title(title) Description(description) } Image(imageUrl) } } } }
Основываясь на примере Card
, обратите внимание, что description
всегда передается в Card
. Несмотря на то, что description
используется только тогда, когда ширина позволяет его отобразить, Card
всегда требует description
, независимо от доступной ширины.
Постоянная передача достаточного количества контента упрощает адаптивные макеты, делая их менее зависимыми от состояния, и позволяет избежать возникновения побочных эффектов при переключении между размерами дисплея (что может произойти из-за изменения размера окна, изменения ориентации или сворачивания и разворачивания устройства).
Этот принцип также позволяет сохранять состояние при изменении макета. Поднимая информацию, которая может не использоваться при всех размерах дисплея, вы можете сохранять состояние приложения при изменении размера макета. Например, вы можете поднять логический флаг showMore
, чтобы состояние приложения сохранялось, когда изменение размера дисплея заставляет макет переключаться между скрытием и отображением контента:
@Composable fun Card( imageUrl: String, title: String, description: String ) { var showMore by remember { mutableStateOf(false) } BoxWithConstraints { if (maxWidth < 400.dp) { Column { Image(imageUrl) Title(title) } } else { Row { Column { Title(title) Description( description = description, showMore = showMore, onShowMoreToggled = { newValue -> showMore = newValue } ) } Image(imageUrl) } } } }
Узнать больше
Чтобы узнать больше об адаптивных макетах в Compose, посетите следующие ресурсы:
Примеры приложений
- CanonicalLayouts — это репозиторий проверенных шаблонов дизайна, которые обеспечивают оптимальный пользовательский опыт на больших дисплеях.
- JetNews показывает, как разработать приложение, адаптирующее свой пользовательский интерфейс для использования доступного пространства на дисплее
- Ответ — адаптивный пример для поддержки мобильных устройств, планшетов и складных устройств.
- Теперь в Android есть приложение, которое использует адаптивные макеты для поддержки различных размеров дисплеев
Видео
- Создавайте пользовательские интерфейсы Android для любого размера экрана
- Форм-факторы | Android Dev Summit '22