Основы адаптивного дизайна на Flutter

Из этого руководства вы узнаете, как с помощью Flutter создавать приложения, которые автоматически подстраиваются под изменение размера и положения экрана пользовательского устройства.

Скачать материалы

Версии программного обеспечения: Dart 2, Flutter 1.7, Android Studio 3.5

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

В этом руководстве мы рассмотрим следующие темы:

  1. Создание чат-приложения, дизайн которого реагирует на изменение параметров экрана.
  2. Использование виджетов MediaQuery, LayoutBuilder, OrientationBuilder, FittedBox и AspectRatio.
  3. Адаптация верстки к изменению положения экрана.
  4. Адаптивное масштабирование текста.
  5. Привязка вложенных виджетов к колонкам.
  6. Изучение концепции виджета CustomMultiChildLayout.

Примечание: это руководство предполагает, что читатель уже знаком с базовыми приемами разработки на Flutter. Если это не так, начните изучение фреймворка со статьи «Начинаем работать с Flutter». Также понадобятся навыки использования Android Studio в связке с Flutter.

Что такое адаптивный дизайн

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

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

Поддержка различных типов устройств и параметров дисплея

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

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

Адаптация интерфейса к всплывающей клавиатуре

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

В частности, Scaffold автоматически меняет размер нижней вставки, освобождая пространство для клавиатуры. Эту функцию можно отключить, если в свойстве resizeToAvoidBottomInset указать параметр false. Подробнее на тему взаимодействия класса Scaffold с клавиатурой можно прочитать здесь.

Обработка изменений положения экрана

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

В Flutter за обработку изменения ориентации отвечает MediaQuery, который помогает автоматически перестраивать макет страницы. Библиотеки MaterialApp и WidgetsApp используют MediaQuery по умолчанию. Подробнее о MediaQuery можно почитать здесь.

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

Приступаем к работе

Загрузите стартовый проект, нажав на кнопку «Скачать материалы». Затем откройте проект в Android Studio 3.4 или выше. Если вы пользуетесь средой разработки VS Code, некоторые инструкции будут отличаться.

У вас должна быть установлена версия Flutter, 1.5 или выше. Не забудьте установить все нужные зависимости; в случае их отсутствия Android Studio продемонстрирует сообщение «Packages get’ has not been run» («Нужные пакеты не были установлены»).

Как вы обнаружите, стартовый пакет уже включает в себя заготовки для различных частей чат-приложения.

Использование MediaQuery для управления макетом

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

Перейдите в директорию lib, откройте файл ChatListPage.dart. Замените код метода build(BuildContext context) на приведенный ниже:

// 1
var hasDetailPage =
    MediaQuery.of(context).orientation == Orientation.landscape;
// 2
Widget child;

if (hasDetailPage) {
  // 3
  child = Row(
    children: [
      // 4
      SizedBox(
        width: 250,
        height: double.infinity,
        child: _buildList(context, hasDetailPage),
      ),
      // 5
      Expanded(child: _buildChat(context, selectedIndex)),
    ],
  );
} else {
  // 6
  child = _buildList(context, hasDetailPage);
}

return Scaffold(
  appBar: AppBar(
    title: Text("Chats"),
  ),
  body: SafeArea(
    // 7
    child: child,
  ),
);

Этот код задает адаптивный дизайн для страницы чата, используя MediaQuery. Рассмотрим подробнее, как именно это происходит:

  1. Сначала код проверяет ориентацию страницы. Если это горизонтальное положение, передаются соответствующие параметры страницы.
  2. Объявляется дочерний виджет для использования на следующих этапах.
  3. Если параметры страницы получены, дочерний виджет становится рядом виджетов.
  4. Ряд содержит список чатов в качестве первого элемента.
  5. Второй элемент ряда отображает страницу чата с диалогами.
  6. Если параметры страницы еще не переданы, дочерний виджет будет списком чатов.
  7. Здесь дочерний виджет становится вложенным виджетом SafeArea.

Запустите выполнение проекта, и вы увидите вот такую страницу в вертикальном положении:

И такую страницу при горизонтальной ориентации экрана:

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

Использование виджетов в адаптивном дизайне

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

Применение LayoutBuilder и OrientationBuilder

LayoutBuilder и OrientationBuilder – альтернатива MediaQuery в части адаптации к ориентации экрана пользовательского устройства. Рассмотрим, как именно они работают. Начнем с LayoutBuilder.

Прежде всего, откройте файл ChatListPage.dart, как вы делали на предыдущем этапе. Замените код build(…) наследующий:

return Scaffold(
  appBar: AppBar(
    title: Text("Chats"),
  ),
  body: SafeArea(
    // 1
    child: LayoutBuilder(builder: (builder, constraints) {
      // 2
      var hasDetailPage = constraints.maxWidth > 600;

      if (hasDetailPage) {
        // 3
        return Row(
          children: [
            // 4
            SizedBox(
              width: 250,
              height: double.infinity,
              child: _buildList(context, hasDetailPage),
            ),
            // 5
            Expanded(child: _buildChat(context, selectedIndex)),
          ],
        );
      } else {
        // 6
        return _buildList(context, hasDetailPage);
      }
    }),
  ),
);

Приведенный выше код заново определяет макет страницы, в этот раз с использованием LayoutBuilder. Рассмотрим подробнее, что именно мы только что сделали:

  1. Сначала вы объявили LayoutBuilder дочерним виджетом SafeArea.
  2. Затем код проверяет, получены ли параметры страницы, если ее максимальная ширина превосходит 600 пикселей. Если ширина родительского виджета больше 600, передаются параметры страницы.
  3. Если параметры уже получены, дочерний виджет становится рядом виджетов.
  4. Первый элемент ряда – список чатов.
  5. Второй элемент показывает диалоги.
  6. И, наконец, если параметры страницы еще не получены, отображается список чатов.

Запустите сборку и выполнение проекта. Как и на предыдущем этапе, вы получите различные макеты страниц при различной ориентации экрана.

Если вы также хотите опробовать OrientationBuilder, замените строки, относящиеся к объявлению LayoutBuilder и настройкам hasDetailPage на следующий код:

child: OrientationBuilder(builder: (builder, orientation) {  
  var hasDetailPage = orientation == Orientation.landscape;

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

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

Далее мы исправим текст, который не масштабируется вместе с аватаром.

Автоматическое масштабирование текста с помощью родительских виджетов

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

Использование FittedBox

Откройте файл AvatarImageView.dart в директории lib/widgets и обратите внимание на код в _buildContent(Color textColor). Здесь находится текстовый виджет, отображающий инициалы пользователя. Размер шрифта – 14. Оберните текстовый виджет в FittedBox, как показано ниже:

// 1
return FittedBox(
  // 2
  fit: BoxFit.contain,
  // 3
  child: Text(
    initials,
    style: TextStyle(color: textColor, fontSize: 14),
  ),
);

Это заставит виджет Text заполнить родительский виджет и следовать правилам в BoxFit.contain. Рассмотрим код построчно:

  1. Сначала вы сделали FittedBox родительским виджетом Text.
  2. Затем вы устанавливаете для contain возможность максимального масштабирования без выхода за границы виджета.
  3. И, наконец, вы сделали Text дочерним виджетом.

Запустите сборку и выполнение проекта. В горизонтальной ориентации вы получите такой макет страницы:

Как видите, теперь текст масштабируется правильно. Для других случаев вы можете использовать другие типы BoxFit. Скриншот, приведенный ниже, показывает различные варианты масштабирования:

Ограничения для вложенных виджетов

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

Пока что такой функции в нашем приложении нет. Нажмите на иконку вложения, и при вертикальном положении экрана вы увидите следующее:

В горизонтальном варианте экран выглядит так:

Использование соотношения сторон AspectRatio

Галерея не работает, и мы займемся исправлением этой ошибки. Прежде всего, откройте файл ConversationPage.dart в директории lib. Найдите строку SquareGallery(), отмеченную примечанием TODO.

Виджет галереи не отображается потому, что сейчас он является вложенным элементом Column и к тому же не имеет параметров для определения собственного размера. Для исправления этой ситуации оберните виджет в AspectRatio для установки соотношения сторон. Замените строку SquareGallery() на следующий код:

AspectRatio(
  aspectRatio: 3,
  child: SquareGallery(),
),

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

Запустите сборку и выполнение программы. Теперь после нажатия на иконку вложения вы получите следующий интерфейс в вертикальном положении экрана:

И следующий дизайн в горизонтальной ориентации:

Примите поздравления: вы исправили все недочеты, связанные с масштабированием. Дизайн чат-приложения стал полностью адаптивным.

Использование макета CustomMultiChildLayout

В дополнение к основным виджетам для создания адаптивного дизайна Flutter предоставляет разработчикам возможность реализовать собственный адаптивный макет с помощью CustomMultiChildLayout. Мы бегло рассмотрим этот способ, но лишь в теории, поскольку CustomMultiChildLayout – очень обширная тема, а здесь мы ограничиваемся основами.

Посмотрите на этот код:

CustomMultiChildLayout(
  delegate: delegate,
  children: widgets,
)

Здесь вы объявляете CustomMultiChildLayout с делегацией функций. Делегатом может быть объект класса, какпоказано ниже:

// 1
class RWDelegate extends MultiChildLayoutDelegate {
  // 2
  @override
  void performLayout(Size size) {
    // Do your layout here
  }

  // 3
  @override
  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false;
}

Вот что делает этот код:

  1. Сначала вы объявляете подкласс MultiChildLayoutDelegate.
  2. Затем переопределяете метод performLayout, который определяет расположение вложенных виджетов на странице с использованием двух других методов, layoutChild и positionChild.
  3. И, наконец, вы получаете логический параметр от shouldRelayout, который определяет, нужно ли заново выполнить раскладку элементов.

Полную документацию по виджету можно изучить здесь, а по работе с делегированием – здесь.

Что еще будет полезно изучить

Все материалы руководства можно скачать, нажав на кнопку «Скачать материалы».

Если тема адаптивного дизайна на Flutter вам интересна, ознакомьтесь с официальной документацией по этой ссылке и подборкой методов дизайна здесь.

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

Скачать материалы

Данная публикация является переводом статьи «Responsive Design for Flutter: Getting Started» , подготовленная редакцией проекта.

Меню