Как настроить адаптивный UI во Flutter
Привет всем Flutter-разработчикам и людям, которые только знакомятся с этой технологией. В этой статье я буду рассказывать о такой теме, как динамический пользовательский интерфейс, который самостоятельно подстраивается под размеры экранов на различных устройствах. В случае любительского или обучающего проекта, эта тема не так важна ввиду малого количества целевых устройств, на которых будет использоваться приложение. В то же время, на полноценном корпоративном проекте отзывчивость пользовательского интерфейса является одной из основных тем и проблем одновременно.
Далеко не каждый заказчик изначально предоставляет список поддерживаемых устройств, а некоторые и вовсе закладывают в проект поддержку планшетов или других специфических экранов. 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 можно найти тут.
2 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів