Урок по треку Flutter и Dart 7 мин

Вёрстка: ограничения вниз, размеры вверх

Ограничения вниз, размеры вверх, Row, Column, Expanded, Stack и прокрутка.

Раскладка во Flutter устроена одним правилом из документации: constraints go down, sizes go up, parent sets position. Ограничения спускаются вниз, размеры поднимаются вверх, позицию задаёт родитель. Почти каждая ошибка вёрстки объясняется этим правилом.

Как работает проход раскладки

  1. Родитель передаёт ребёнку BoxConstraints: минимальную и максимальную ширину и высоту.
  2. Ребёнок выбирает свой размер внутри этих ограничений и сообщает его родителю.
  3. Родитель решает, где ребёнка расположить.

Ребёнок не знает своей позиции и не может выбрать размер вне ограничений. Поэтому Container(width: 100) на весь экран растягивается, если родитель дал жёсткие ограничения (tight): например, корень приложения требует ровно размер экрана. Чтобы ребёнок мог быть меньше, нужен родитель, ослабляющий ограничения: Center, Align, UnconstrainedBox.

  • Tight: min = max, размер задан жёстко.
  • Loose: min = 0, ребёнок может быть меньше максимума.
  • Unbounded: max = бесконечность. Так Column даёт детям высоту, а ListView даёт длину по оси прокрутки.

Row и Column

Row и Column это Flex. Раскладка в два шага: сначала дети без flex получают неограниченный размер по главной оси и меряют себя, потом оставшееся место делится между Expanded и Flexible пропорционально flex.

  • Expanded заставляет ребёнка занять всю свою долю (tight).
  • Flexible разрешает занять до своей доли (loose).
  • mainAxisAlignment и crossAxisAlignment двигают детей, mainAxisSize: MainAxisSize.min сжимает сам Row по содержимому.

Три классические ошибки

Жёлто-чёрная полоса «RenderFlex overflowed». Дети Row шире экрана: длинный текст не переносится, потому что получил неограниченную ширину. Лечится Expanded/Flexible вокруг текста или overflow: TextOverflow.ellipsis внутри Expanded.

«Vertical viewport was given unbounded height». ListView положили прямо в Column. Column даёт ему бесконечную высоту, а список хочет занять всю доступную. Решения: обернуть в Expanded (чаще всего правильно), задать высоту через SizedBox, или shrinkWrap: true (дорого для длинных списков: строит все элементы).

«BoxConstraints forces an infinite width». Ребёнок хочет бесконечную ширину внутри неограниченного родителя, например TextField в Row без Expanded.

Stack и позиционирование

Stack кладёт детей друг на друга. Непозиционированные дети выравниваются по alignment, Positioned задаёт отступы от краёв. Размер Stack по умолчанию определяется непозиционированными детьми. Всё, что выходит за его границы, обрезается (clipBehavior: Clip.hardEdge по умолчанию).

Прокрутка и слайверы

ListView.builder строит только видимые элементы, поэтому подходит для длинных списков. SingleChildScrollView с Column строит всё сразу: только для коротких экранов вроде формы. Для сложных экранов (сворачиваемый заголовок, сетка и список в одной прокрутке) используют CustomScrollView со слайверами: SliverAppBar, SliverList, SliverGrid. Слайвер получает не BoxConstraints, а SliverConstraints: сколько уже прокручено и сколько видно.

Адаптивность

MediaQuery.sizeOf(context) даёт размер экрана и подписывает только на размер, а не на весь MediaQuery (меньше перестроений, чем MediaQuery.of). LayoutBuilder даёт ограничения от родителя, что точнее для компонента внутри панели. SafeArea убирает вырезы и системные панели.

Что спросят на собесе

  • Сформулируйте правило раскладки и объясните его на примере.
  • Чем Expanded отличается от Flexible.
  • Почему ListView внутри Column падает и как это исправить.
  • Tight, loose и unbounded ограничения.
  • Когда нужен CustomScrollView и слайверы, чем плох shrinkWrap.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.