Прийшов час осідлати справжнього Буцефала🏇🏻Приборкай норовливого коня разом з Newxel🏇🏻Умови на сайті
×Закрыть

Как настроить адаптивный UI во Flutter

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

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

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

Основные подходы

«Широкий функционал» в случае с Flutter действительно означает очень большое количество фич и вариантов их использования. Также разные подходы по-разному влияют на вёрстку как со стороны кода, так и со стороны непосредственно видимого интерфейса. Соответственно, имеет смысл условно разделить «адаптивный» функционал на несколько подходов:

  • Flex-подход,
  • ScreenUtil,
  • LayoutBuilder;

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

Flex-подход

Весьма недооцененный подход, хоть и один из самых интуитивных. Вообще, Flex-подход не является официальным названием, а скорее внутренним обозначением нашей компании. Данный подход не требует сторонних пакетов и базируется на «трёх китах»:

  • Flexible Widget,
  • Expanded Widget,
  • Spacer Widget;

Искушенный пользователь уже понял о чём пойдёт речь, и что общего у этих трех виджетов. Все три виджета имеют flex-фактор в качестве параметра(даже Spacer!), отсюда и происходит название подхода. Сам по себе flex-подход является очень мощным инструментом для разработчика — он один способен покрыть большинство кейсов, связанных с адаптивным интерфейсом, однако порой ценой большого количества усилий и времени. Проводить глубокий анализ каждого из этих виджетов мы не будем, так как это просто-напросто не имеет никакого смысла. Expanded и Spacer по своей сути являются фасадами для Flexible, в чём можно удостовериться, изучив код самого SDK (кстати хорошая практика, там очень много полезных и веселых вещей).

Expanded — Flexible, но с параметром FlexFit.tight по-умолчанию

Параметр fit также очень прост — в него можно передать только FlexFit.tight и FlexFit.loose. Соответственно Flexible будет занимать всё доступное место(tight) или, может быть, меньше доступного места(loose). Flexible в чистом виде встречается достаточно редко, обычно используются Expanded и Spacer.

Spacer — да, это действительно то, чем кажется: Expanded и SizedBox

Первое, что нам понадобится для использования Flex-подхода — самый обычный Column или Row. В качестве примера возьмем очень простой случай, для большей наглядности, — пара Container-виджетов в Row с отступом между ними и отступами по краям.

Прежде всего, для этого нам потребуется обернуть оба контейнера в Expanded. Безусловно, похожее поведение можно воссоздать при помощи всем хорошо известных параметров mainAxisAlignment/crossAxisAlignment, однако AxisAlignment в большинстве случаев работает с виджетами, имеющими статичные размеры и является скорей вспомогательной фичей. В нашем же случае контейнеры не имеют размера вовсе — их размер переопределяется родительскими Expanded-виджетами. Результат должен выглядеть примерно таким образом:

Довольно просто, правда? За размер спейсеров и экспандедов, точнее за их пропорцию, как раз и отвечает параметр flex. По умолчанию, все flex-виджеты имеют flex равный 1, если не задано другое значение. Соответственно, на примере выше каждый из экспандедов будет в три раза больше любого спейсера, а спейсеры равны между собой (как и экспандеды).

Если объяснять работу flex-параметра, используя аналогию с простым примером из реальной жизни, то идеально подойдёт барное дело или кулинария, где рецепты описываются в формате «3 части Х, 2 части Y, 4 части Z». В нашем случае это звучало бы примерно так (сверху-вниз): «1 часть Spacer, 3 части Expanded, 1 часть Spacer, 3 части Expanded, 1 часть Spacer».

Казалось бы, что может пойти не так? Как ни странно, у Flex-подхода есть достаточно много фундаментальных проблем. Во-первых, Container, «обжатый» спейсерами принимает размер child-виджета, а Container в Expanded принимает размер, заданный экспандедом. Во-вторых, если мы просто добавим наш Row с контейнерами в безразмерный Column, то Row уменьшится до некорректно малых размеров или вовсе исчезнет.

Да уж, неприятно

Почему же это произошло? Дело в том, что Expanded/Flexible, завёрнутый в Row переназначает только ширину, а завёрнутый в Column — только высоту соответственно. Т.е., по одной из осей мы должны использовать статические размеры либо обернуть наш Row в еще один экспандед и дополнительно работать с flex’ами колонны. Это значительно усложняет работу и «вскрывает» большое количество нюансов.

Рассмотрим еще один пример неправильного функционирования. В прошлом кусочке кода TestContainer не имел значения высоты по умолчанию. В данном примере зададим ему значение по умолчанию равное 100, а Row обернем в Expanded и Сolumn. Помимо этого добавим в колонну снизу Spacer, а в Expanded дадим flex равный двум.

Конструктор нашего контейнера

В этом коде кое-чего не хватает. Об этом далее

Как и ожидалось, что-то пошло не так

Объяснить происходящее достаточно просто. Как я и сказал ранее, Expanded/Flexible в Row переназначает только ширину, что и произошло в данной ситуации, так как мы добавили в контейнер значение параметра по умолчанию. Исправить это достаточно просто — убрать параметр либо передать в него null.

В нашем случае такой вариант будет отображаться правильно

Кроме этого, Flex-подход плохо «дружит» со scrollable-виджетами, ввиду того, что Flex-вёрстка обычно не имеет или почти не имеет статических размеров, а scrollable в свою очередь требует статический размер для дочерних виджетов. Это приводит к ошибке, когда scrollable не может получить актуальный размер и принимает бесконечные размеры, тем самым ломая приложение.

Пришло время подвести небольшой итог по Flex-подходу.

Преимущества

  • Полная адаптивность,
  • Не требуются сторонние пакеты,
  • Гибкость и универсальность подхода,
  • Практически отсутствует избыточность со стороны кода.

Недостатки

  • Плохо взаимодействует со scrollable,
  • Высокие трудозатраты,
  • Большое количество «подводных камней»,
  • Подход значительно усложняется, если требуются динамические размеры сразу по обеим осям.

В следующей части мы рассмотрим такой пакет, как ScreenUtil и проанализируем его по аналогии с Flex-подходом.

Также в конце каждой статьи я буду оставлять простейший пример на своём репозитории. Примеры используют пакет DeviceSimulator, так как он идеально подходит для демонстрации UI на различных устройствах. Единственное что вам требуется про него знать — для правильного функционирования пример нужно запускать на планшете(реальном или эмуляторе) с максимально большим размером экрана.

Работа Device Simulator

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

Пример № 1 можно найти тут.

LinkedIn

Похожие статьи

Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

а как дела во флатере с изменяемым размерами окна (как в ios так и в Андроиде) и фолдабл устройствами? в первую очередь возможно ли использовать информацию из системы (class sizes в ios и тд)?

Дякую за статтю! Було цікаво. З Expanded теж багато наловив шишок, якщо в нього засовувати Row/Column. Стороніми лібами принципово не користуюсь, бо вони або дохнуть з часом, або треба чекати, коли оновлять до нового флатера/дарта. А зав’язувати основний лейаут на підробку на колінах теж не хочеться :)

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

Подписаться на комментарии