Адаптивный дизайн с представлениями

Попробуйте Compose
Jetpack Compose – это рекомендуемый набор инструментов для создания пользовательского интерфейса в Android. Узнайте, как работать с адаптивными макетами в Compose.

Адаптивные макеты обеспечивают удобство работы независимо от размера экрана. Реализуйте адаптивные макеты, чтобы приложение на основе представлений поддерживало все размеры, ориентации и конфигурации экрана, включая изменяемые, например многооконный режим.

Адаптивный дизайн

Первый шаг в поддержке различных форм-факторов устройств – создание макета, который реагирует на изменения в объеме пространства дисплея, доступного для вашего приложения.

ConstraintLayout

Лучший способ создать адаптивный макет – использовать ConstraintLayout в качестве базового макета для пользовательского интерфейса. ConstraintLayout позволяет указать положение и размер каждого представления в соответствии с пространственными отношениями с другими представлениями в макете. Все представления могут перемещаться и изменять размер вместе по мере изменения пространства отображения.

Проще всего создать макет с помощью ConstraintLayout в редакторе макетов в Android Studio. Редактор макетов позволяет перетаскивать новые представления в макет, применять ограничения относительно родительских и дочерних представлений и задавать свойства представлений, не редактируя XML вручную.

Рисунок 3. Редактор макетов в Android Studio с открытым файлом ConstraintLayout.

Подробнее о том, как создать адаптивный интерфейс с помощью ConstraintLayout…

Адаптивная ширина и высота

Чтобы макет адаптировался к разным размерам экрана, используйте для ширины и высоты компонентов представления wrap_content, match_parent или 0dp (match constraint) вместо заданных в коде значений.

  • wrap_content: размер представления устанавливается в соответствии с его содержимым.
  • match_parent – представление расширяется настолько, насколько это возможно в родительском представлении.
  • 0dp (match constraint): в ConstraintLayout, аналогично match_parent. Представление занимает все доступное пространство в пределах ограничений.

Пример:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/lorem_ipsum" />

На рисунке 4 показано, как ширина и высота TextView корректируются в зависимости от ширины экрана при изменении ориентации устройства.

Рисунок 4. Адаптивный TextView.

Для ширины TextView задано значение match_parent, чтобы заполнить все доступное пространство, а для высоты – wrap_content, чтобы она соответствовала высоте текста. Это позволяет адаптировать представление к разным размерам экрана и объемам текста.

Если вы используете LinearLayout, вы также можете расширить дочерние представления на основе веса макета, чтобы представления пропорционально заполняли доступное пространство. Однако использование весов во вложенном LinearLayout требует от системы выполнения нескольких проходов макета для определения размера каждого представления, что снижает производительность пользовательского интерфейса.

ConstraintLayout позволяет создавать практически любые макеты, доступные в LinearLayout, но без ущерба для производительности. Поэтому преобразуйте вложенные макеты LinearLayout в ConstraintLayout. Затем вы можете определить взвешенные макеты с цепочками ограничений.

Адаптивный дизайн

Макет приложения должен быть адаптивным и подстраиваться под разные размеры экрана. Однако даже адаптивный макет не может обеспечить наилучшее взаимодействие с пользователем на каждом устройстве или в многооконном режиме. Например, интерфейс, разработанный для телефона, вероятно, не обеспечивает оптимального взаимодействия с пользователем на планшете. Адаптивный дизайн предусматривает альтернативные макеты, оптимизированные для разных размеров экрана.

SlidingPaneLayout для интерфейсов со списком и подробными сведениями

Интерфейс со списком и подробными сведениями обычно по-разному выглядит на экранах разных размеров. На больших экранах список и панель подробностей обычно расположены рядом. Когда пользователь выбирает элемент в списке, информация о нем отображается на панели подробностей, а интерфейс не меняется – обе панели остаются рядом. Однако на небольших экранах эти две панели отображаются отдельно, каждая из которых занимает всю область отображения. Когда вы выбираете элемент в списке, панель подробностей (с информацией о выбранном элементе) заменяет панель списка. При обратной навигации панель подробностей заменяется списком.

SlidingPaneLayout управляет логикой, которая определяет, какой из двух вариантов интерфейса подходит для текущего размера окна:

<?xml version="1.0" encoding="utf-8"?>
<androidx.slidingpanelayout.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/item_navigation" />

</androidx.slidingpanelayout.widget.SlidingPaneLayout>

Поведение SlidingPaneLayout определяется атрибутами layout_width и layout_weight двух представлений, содержащихся в SlidingPaneLayout. В примере, если окно достаточно велико (ширина не менее 580dp) для отображения обоих представлений, панели отображаются рядом. Но если ширина окна меньше 580 dp, панели сдвигаются друг на друга, чтобы по отдельности занимать все окно приложения.

Если ширина окна больше общей указанной минимальной ширины (580 dp), для пропорционального изменения размера двух панелей можно использовать значения layout_weight. В примере панель списка всегда имеет ширину 280dp, поскольку у нее нет веса. Однако панель подробностей всегда заполняет любое горизонтальное пространство за пределами 580 dp из-за настройки layout_weight.

Альтернативные ресурсы разметки

Чтобы адаптировать дизайн интерфейса к экранам разных размеров, используйте альтернативные макеты, определяемые квалификаторами ресурсов.

Рисунок 5. Одно и то же приложение с разными макетами для разных размеров экрана.

Чтобы создать адаптивные макеты для разных экранов, добавьте в исходный код приложения дополнительные каталоги res/layout/. Создайте каталог для каждой конфигурации экрана, для которой требуется другой макет. Затем добавьте квалификатор конфигурации экрана к названию каталога layout (например, layout-w600dp для экранов с доступной шириной 600 dp).

Квалификаторы конфигурации представляют видимое пространство экрана, доступное для интерфейса приложения. При выборе макета для приложения система учитывает все системные элементы (например, панель навигации) и изменения конфигурации окна (например, многооконный режим).

Чтобы создать альтернативные макеты в Android Studio, ознакомьтесь с разделом Используйте варианты макетов для оптимизации под разные экраны в статье Разработка пользовательского интерфейса с помощью представлений.

Модификатор наименьшей ширины

Модификатор размера экрана Наименьшая ширина позволяет создавать альтернативные макеты для экранов с минимальной шириной, измеряемой в пикселях, не зависящих от плотности (dp).

Описывая размер экрана в dp, Android позволяет создавать макеты, предназначенные для определенных размеров дисплея, не беспокоясь о различной плотности пикселей.

Например, вы можете создать макет с названием main_activity, оптимизированный для телефонов и планшетов, создав разные версии файла в разных каталогах:

res/layout/main_activity.xml           # For phones (smaller than 600dp smallest width)
res/layout-sw600dp/main_activity.xml   # For 7" tablets (600dp wide or wider)

Модификатор наименьшей ширины указывает наименьшую из двух сторон дисплея независимо от текущей ориентации устройства, поэтому это способ указать общий размер дисплея, доступный для вашего макета.

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

  • 320 dp: небольшой экран телефона (240 x 320 ldpi, 320 x 480 mdpi, 480 x 800 hdpi и т. д.).
  • 480 dp: большой экран телефона ~5" (480 x 800 mdpi)
  • 600 dp: планшет с экраном 7 дюймов (600 x 1024 mdpi)
  • 720 dp: планшет с экраном 10 дюймов (720 x 1280 mdpi, 800 x 1280 mdpi и т. д.).

На рисунке ниже показано, как разные значения ширины экрана в dp соответствуют разным размерам и ориентациям экрана.

Рисунок 6. Рекомендуемые контрольные точки ширины для поддержки разных размеров экрана.

Значения для модификатора минимальной ширины указываются в dp, поскольку важен размер доступного пространства на экране после того, как система учтет плотность пикселей (а не исходное разрешение в пикселях).

Размеры, которые вы указываете с помощью квалификаторов ресурсов, например наименьшей ширины, не являются фактическими размерами экрана. Размеры указывают ширину или высоту в единицах dp, которые доступны для окна приложения. Часть экрана может быть занята системным интерфейсом Android (например, системной строкой внизу или строкой состояния вверху), поэтому она может быть недоступна для вашего макета. Если приложение используется в многооконном режиме, оно имеет доступ только к размеру окна, в котором оно находится. При изменении размера окна запускается изменение конфигурации с новым размером окна, что позволяет системе выбрать подходящий файл макета. Поэтому в квалификаторах ресурсов нужно указывать только то пространство, которое требуется вашему приложению. Система сама учтет пространство, необходимое для системного интерфейса.

Модификатор доступной ширины

Вместо того чтобы менять макет в зависимости от наименьшей ширины экрана, вы можете менять его в зависимости от доступной ширины или высоты. Например, вы можете использовать макет с двумя панелями, когда ширина экрана составляет не менее 600 dp. Это может зависеть от того, находится ли устройство в альбомной или портретной ориентации. В этом случае следует использовать модификатор available width, как показано ниже.

res/layout/main_activity.xml         # For phones (smaller than 600dp available width)
res/layout-w600dp/main_activity.xml  # For 7" tablets or any screen with 600dp available width
                                     # (possibly landscape phones)

Если для вашего приложения важна доступная высота, вы можете использовать квалификатор available height. Например, layout-h600dp для экранов высотой не менее 600 dp.

Модификаторы ориентации

Даже если вы можете поддерживать все варианты размеров, используя только комбинации модификаторов наименьшей ширины и доступной ширины, вы также можете изменить пользовательский интерфейс, когда пользователь переключается между портретной и альбомной ориентациями.

Для этого добавьте в названия каталогов макетов модификаторы port или land. Главное, чтобы модификаторы ориентации указывались после модификаторов размера. Пример:

res/layout/main_activity.xml                # For phones
res/layout-land/main_activity.xml           # For phones in landscape
res/layout-sw600dp/main_activity.xml        # For 7" tablets
res/layout-sw600dp-land/main_activity.xml   # For 7" tablets in landscape

Дополнительную информацию о квалификаторах конфигурации экрана можно найти в статье Обзор ресурсов приложения.

Классы размеров окон

Классы размеров окон – это контрольные точки области просмотра, которые помогают создавать адаптивные макеты. Контрольные точки определяют доступную для приложения область отображения как компактную, среднюю или расширенную. Ширина и высота указываются отдельно, поэтому у приложения всегда есть класс размера окна по ширине и класс размера окна по высоте.

Чтобы применить адаптивные макеты программно, выполните следующие действия:

  • Создайте ресурсы макета на основе контрольных точек класса размера окна
  • Вычислите классы размеров окна по ширине и высоте, используя функцию WindowSizeClass#compute() из библиотеки Jetpack WindowManager.
  • Разверните ресурс разметки для текущих классов размера окна.

Подробнее о классах размеров окон…

Модульные компоненты интерфейса с использованием фрагментов

При разработке приложения для экранов разных размеров используйте фрагменты, чтобы выделить логику интерфейса в отдельные компоненты. Это позволит избежать ненужного дублирования поведения интерфейса в разных действиях. Затем вы можете объединить фрагменты, чтобы создать макеты с несколькими панелями на больших экранах, или разместить фрагменты в отдельных действиях на маленьких экранах.

Например, шаблон списка с подробными сведениями (см. SlidingPaneLayout выше) можно реализовать с помощью одного фрагмента, содержащего список, и другого фрагмента, содержащего сведения об элементе списка. На больших экранах фрагменты могут отображаться рядом, а на маленьких – по отдельности, заполняя экран.

Подробнее о фрагментах…

Встраивание объектов activity

Если ваше приложение состоит из нескольких действий, встраивание действий позволяет легко создать адаптивный пользовательский интерфейс.

Встраивание Activity позволяет одновременно показывать несколько Activity или несколько экземпляров одного Activity в окне задачи приложения. На больших экранах действия могут отображаться рядом, а на маленьких – друг над другом.

Вы определяете, как приложение отображает свои действия, создавая XML-файл конфигурации, который система использует для определения подходящего представления на основе размера дисплея. Вы также можете использовать вызовы Jetpack WindowManager API.

Встраивание действий поддерживает изменение ориентации устройства и складные устройства, складывание и раскладывание действий по мере вращения, складывания и раскладывания устройства.

Подробнее о встраивании действий…

Размеры экрана и соотношение сторон

Протестируйте приложение на экранах разных размеров и с разным соотношением сторон, чтобы убедиться, что интерфейс масштабируется правильно.

Android 10 (уровень API 29) и более поздние версии поддерживают широкий диапазон соотношений сторон. Форм-факторы складных устройств могут варьироваться от высоких узких экранов, например 21:9 в сложенном виде, до квадратного соотношения сторон 1:1 в разложенном виде.

Чтобы обеспечить совместимость с как можно большим количеством устройств, протестируйте приложения на экранах со следующими соотношениями сторон:

Рисунок 7. Различные соотношения сторон экрана.

Если у вас нет доступа к устройствам со всеми размерами экрана, которые вы хотите протестировать, вы можете использовать эмулятор Android, чтобы эмулировать практически любой размер экрана.

Если вы хотите провести тестирование на реальном устройстве, но у вас его нет, воспользуйтесь Firebase Test Lab. Этот сервис позволяет получить доступ к устройствам в центре обработки данных Google.

Дополнительные ресурсы