Здравы будьте! С вами на связи руководитель Flutter-направления Mad Brains Николай Омётов. В этой статье я проведу разбор особенностей вёрстки отступов с помощью Padding и SizedBox и расскажу, что выбрала наша команда для создания единого стиля кода.
Вообще, во Flutter есть несколько способов создать свободное пространство между элементами. Самыми распространёнными являются Padding и SizedBox. Технически использовать можно оба, а иногда их даже комбинируют. Но мы приверженцы красивого кода, хотим сделать его понятнее, чище для чтения и рефакторинга. Мы провели мини-исследование и выяснили, какой виджет более универсален для этой цели.
Пример ситуации
Было
Должно стать
SizedBox решение:
Row(
children: [
Expanded(
child: ColoredBox(
color: Colors.red,
child: Center(
child: Text('1' * 1000),
),
),
),
// Пустота
const SizedBox(width: 16),
Expanded(
child: ColoredBox(
color: Colors.green,
child: Center(
child: Text('2' * 1000),
),
),
),
// Пустота
const SizedBox(width: 16),
const ColoredBox(
color: Colors.yellow,
child: Icon(Icons.check),
),
],
),
Padding решение:
Row(
children: [
Expanded(
// Пустота
child: Padding(
padding: const EdgeInsets.only(right: 8),
child: ColoredBox(
color: Colors.red,
child: Center(
child: Text('1' * 1000),
),
),
),
),
Expanded(
// Пустота
child: Padding(
padding: const EdgeInsets.only(left: 8),
child: ColoredBox(
color: Colors.green,
child: Center(
child: Text('2' * 1000),
),
),
),
),
// Пустота
const Padding(
padding: EdgeInsets.only(left: 16),
child: ColoredBox(
color: Colors.yellow,
child: Icon(Icons.check),
),
),
],
),
Сравниваем Padding и SizedBox
Название
Мы чаще читаем код чем пишем его. Поэтому название должно быть однозначным и конкретным.
Padding говорит, что добавляет «заполнитель» (что и нужно в нашем случае). Поэтому название говорит само за себя.
SizedBox говорит, что добавляет коробочку какого-то размера. И эта коробочка как раз таки, может использоваться как «заполнитель», но это неочевидно из названия. У SizedBox может быть несколько назначений в коде (задать конкретный размеры ребёнку, заполнить пустое пространство на максимум, заглушка для пустого виджета (привет, shrink) и т.д.), когда быстрым взглядом читаешь это название, то не всегда можешь однозначно сказать, что виджет применяется тут для создания отступа.
Вывод: название у Padding лучше отражает цель вёрстки.
Читаемость
Padding позволяет видеть дерево виджетов, но зачастую не позволяет оценить общее направление пустот без запуска кода и DevTools. Если он опоясывает один из похожих виджетов, то в дереве смотрится неказисто из-за того, что один из них выдвинут на уровень дальше. Но в тоже время Padding обособляет элементы и их можно рассматривать более независимо. А SizedBox похож на хвост, наличие которого приходится всегда проверять. Особенно при перемещении виджета.
SizedBox помогает оценить последовательность элементов в списке и увидеть без запуска кода, как будет отображена колонка или строка. Но он растягивает список: то есть не выстраивается в дерево. Также есть ограничения для Column => SizedBox(width) и Row => SizedBox(height).
Вывод: Padding больше вписывается в стиль Flutter, так как поддерживает вид «дерева» виджетов.
Добавление
Существует кнопка «Wrap with Padding» или подобная, встроенная в IDE: создаётся фиксированный паддинг, который нужно переделывать под ситуацию.
Если для SizedBox сделать снипеты «sw» и «sh», то можно будет быстро его создать:
const SizedBox(width: Курсор,);
Вывод: большой разницы нет, всё создаётся быстро.
Удаление
В SizedBox — 1 или 3 строки в зависимости от форматирования.
[
const SizedBox(width: 1),
const SizedBox(
width: 1,
),
],
Через Padding нужно удалять строки сверху и строку снизу (закрывающая скобка), а она может быть далеко. Если удалять не вручную, то есть кнопка «Remove widget» или подобная. Так что больших различий в удалении по сравнению с SizedBox нет.
Количество строк кода
Padding — от 3-х до 8-ми и более строк, SizedBox — от 1-ой до 3-х в зависимости от форматирования.
Количество в списке
Padding: от одного (когда меняется уровень в дереве одного из элементов) до всех, как в начальном примере, когда мы должны сохранить отношение в Expanded.
SizedBox: чаще всего от n-1, где n — количество элементов, которые нужно разделить.
Ситуации бывают разные, поэтому сложно дать оценку частоте.
Отображение в DevTools
Padding
SizedBox
При использовании Padding отступ четко виден, это удобнее.
Константность
Если Padding и его «ребёнок» (child) объявлены как const, они не будут пересоздаваться при каждом вызове build(). EdgeInsets (класс, который используется для задания отступов) зачастую const.
В SizedBox почти в 100% наших случаев — const. Значит, вычислен заранее. Но так как могут в приложении быть разными (width: 4, 6, 8, 12, 16, 20 ...), то не всегда происходит переиспользование.
Вывод: SizedBox канонизирован почти всегда, а Padding редко.
Гибкость
У EdgeInsets в Padding есть несколько конструкторов для задания отступов:
all(double value): одинаковый отступ со всех сторон;
symmetric({double vertical, double horizontal}): задает отступы вертикально и горизонтально;
only({double left, double top, double right, double bottom}): позволяет задать отступы для каждой стороны отдельно;
fromLTRB(double left, double top, double right, double bottom): аналогично only, но с позиционными аргументами.
В SizedBox только два основных параметра для настройки — width и height, за исключением child.
Вывод: Padding даёт больше возможностей.
Включение виджета в список (возможность добавить / убрать по условию)
Padding (просто):
if(isEnabled)
Padding(child: child)
SizedBox (костыльно):
if (isEnabled)
...[
Widget(),
SizedBox(),
]
Не используйте в этом случае SizedBox или замените уже существующий на Padding.
Работа с динамическими спискамиЗдесь мы не берём в расчёт большие списки данных, которые нужно скроллить или пагинировать. Рассматриваются списки небольшого количества одинаковых элементов: 5 звёзд, точки для для ввода пин-кода и прочее. В данном случае используются генераторы списков.
В случае с SizedBox необходимо чередовать виджеты и разделитель, при этом учитывается количество элементов 2n-1, где n - количество виджетов, которые отделяли.
Для Padding будет достаточно обернуть элемент, и EdgeInsets указать общие размеры. Для крайних элементов нужно будет учесть случаи кастомного паддинга, что тоже является нюансом про который стоит помнить.
Производительность
Существенных различий выявлено не было. Можно заметить только нюанс при работе с массивами: количество элементов детей меньше при использовании Padding, чем SizedBox. Это влияет на работу метода build, когда происходит процесс создания элементов и сравнения их при перестроении дерева. Если смотреть со стороны асимптотической сложности этих действий, то оно одинаковое и будет O(n), где n — количество элементов в списке детей.
Итог: Padding лучше! Почему?
По назначению и функциональности он идеально подходит для нашей задачи писать код красиво. По сути Padding делает пустоту частью элемента, а SizedBox отделяет пустоту от элемента, делает их несвязанными.
SizedBox выглядит больше как удобная хитрость, которая иногда показывает свои минусы (которые есть и у Padding). Но если стандартизировать кодстайл, то мы за Padding. А вы?
Комментарии (4)
ReinRaus
03.11.2023 16:14В данном кейсе вариант с SizedBox изначально плох тем, что когда Вы поймёте, что 2 блока в одной строке не влезает на маленький экран и захотите начиная с кого-то размера экрана сделать не строку, а колонку, то начнутся проблемы и много условной вёрстки. В то же время с Padding всё будет значительно проще.
paamayim
03.11.2023 16:14Не думаю что от замены SizedBox на Padding код станет сильно чище и читабельнее. Как по мне в списках все же удобнее использовать SizedBox. Я использую SizedBox когда мне нужен только один отступ между двумя элементами с любой стороны, когда нужно больше использую Padding. Когда используется Padding надо еще подумать обернуть им виджет сверху для отступа снизу или виджет снизу для отступа сверху (тоже самое для отступов по горизонтале). Должно быть четкое понимание какому виджету нужен отступ иначе при рефакторинге вы рискуете потратить какое то время на поиск того виджета в который вы этот отступ добавили. Еще часто бывает что виджет переиспользуется. Он может быть изначально задуман таким а может таким оказаться впоследствии. И отступы для него могут быть разные на разных экранах. Если в коде виджета корневым элементом будет Padding, а кто то потом еще и условия добавит для различных отступов этого виджета на разных экранах, вместо того чтобы прописать отступы на самих экранах то это уже будет захламление кода. И вот эти вот вещи важнее как по мне. Правильная композиция и правильное применение отступов. Если мне на работе скажут использовать только Padding то это не будет для меня проблемой, однако в некоторых случаях SizedBox для меня предпочтительнее
ekuleshov
03.11.2023 16:14Для полноты картины стоит упомянуть другие решения той же проблемы не упомянутые в статье.
Пакет недели gap. Как SizedBox, но более универсальный. https://www.youtube.com/watch?v=MqjCIITfCIA
Управление промежутками дочерних виджетов из родительского контейнера. Приверженцы красивого кода особенно оценят что не нужно добавлять отступы индивидуально для каждого элемента. Из коробки, например, Wrap. Дополнительные варианты контейнеров можно найти в assorted_layout_widgets ColumnSuper, RowSuper, WrapSuper, и т.п.
Pinkerton42
Буквально вчера на ютуб-канале flutter появился ролик про виджет Gap(XX), но, к сожалению, как сторонний пакет. В комментариях к видео уже предлагают включить его в число стандартных.