Поддержка различных размеров дисплеев обеспечивает доступ к вашему приложению с самых разных устройств и для наибольшего числа пользователей.
Чтобы поддерживать как можно больше разрешений дисплеев — будь то экраны разных устройств или окна приложений в многооконном режиме, — создавайте адаптивные макеты приложений. Адаптивные макеты обеспечивают оптимальный пользовательский опыт независимо от размера дисплея, позволяя вашему приложению работать на телефонах, планшетах, складных устройствах, устройствах 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, /* ... */ ) }
Многоуровневый подход ограничивает логику управления размером экрана одним местом, а не разбрасывает её по всему приложению во многих местах, которые необходимо синхронизировать. Единое место создаёт состояние, которое можно явно передать другим компонуемым объектам, как и любое другое состояние приложения. Явная передача состояния упрощает отдельные компонуемые объекты, поскольку они принимают класс размера окна или указанную конфигурацию вместе с другими данными.
Гибкие вложенные компонуемые объекты можно использовать повторно
Компонуемые элементы более удобны для повторного использования, когда их можно разместить в самых разных местах. Если компонуемый элемент должен быть размещен в определенном месте и иметь определенный размер, то он вряд ли будет повторно использован в других контекстах. Это также означает, что отдельные повторно используемые компонуемые элементы должны избегать неявной зависимости от глобальной информации о размере экрана.
Представьте себе вложенный компонуемый объект, реализующий макет списка-детали , который может отображать либо одну панель, либо две панели рядом:

Решение о детализации списка должно быть частью общего макета приложения, поэтому решение передается вниз от компонуемого элемента на уровне контента:
@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