Flexible — виджет Flutter, который управляет тем, как дочерний элемент занимает доступное пространство в Flex-контейнере. В отличие от Expanded, Flexible не принуждает ребёнка заполнять всю выделенную область — дочерний виджет может быть меньше, и оставшееся место остаётся пустым. По данным официальной документации Flutter (2026), Flexible необходим в сценариях, где требуется гибкое распределение пространства без принудительного растягивания: кнопки с минимальной шириной, текстовые блоки с естественным размером и элементы с intrinsic constraints.
Главное
Flexible — это виджет-обёртка, предоставляющий дочернему элементу гибкое пространство в Flex-контейнере. Flexible, как и Expanded, участвует в распределении свободного места, но с ключевым отличием: ребёнок Flexible может занять меньше места, чем ему выделено, если его собственный размер меньше доступного.
Flexible использует Flex-алгоритм Flutter для вычисления доступного пространства. Сначала фреймворк опрашивает всех негибких детей (виджеты без Flexible/Expanded) и вычисляет их intrinsic size. Оставшееся пространство делится между гибкими детьми пропорционально их flex-факторам. После выделения пространства Flexible проверяет fit — если он loose, к дочернему элементу применяется constraint "не больше выделенного", позволяя ему остаться маленьким.
Особенность Flexible: если дочерний виджет имеет собственные минимальные размеры (например, Text с длинной строкой), Flexible выделяет ему запрошенное пространство, а не растягивает принудительно. Это делает Flexible незаменимым для элементов, размер которых должен определяться контентом, а не контейнером.
Вывод: используйте Flexible, когда нужно резервировать пространство для элемента, но не принуждать его к растягиванию. Это сохраняет естественные размеры контента при гибком распределении макета.
FlexFit — перечисление из двух значений: tight и loose. От выбора fit зависит, будет ли дочерний виджет принудительно растянут до размеров выделенного пространства.
FlexFit.tight: дочерний элемент получает tight constraints — минимальный и максимальный размеры равны выделенному пространству. Ребёнок обязан занять ровно столько места, сколько выделил Flex-алгоритм. Если ребёнок меньше — он растягивается. Если больше — он сжимается. Поведение идентично Expanded.
FlexFit.loose: дочерний элемент получает loose constraints — минимальный размер равен 0, максимальный равен выделенному пространству. Ребёнок может быть любого размера от 0 до maximum. Если ребёнок маленький (например, Text с короткой строкой), он занимает свою естественную ширину, а остаток пространства остаётся пустым.
Вывод: FlexFit.loose — выбор для текстовых блоков и элементов с натуральным размером. FlexFit.tight — для строгих колоночных макетов, где все элементы должны быть одинаковой ширины или высоты.
Flexible и Expanded выглядят похоже, но решают разные задачи. Expanded всегда растягивает ребёнка на полное доступное пространство. Flexible может оставить пустое место вокруг ребёнка, если размер контента меньше выделенной доли.
Сценарий для Flexible: внутри Row расположены две кнопки. Первая — "Купить" (короткий текст), вторая — "Добавить в корзину" (длинный текст). С Expanded обе кнопки растянутся на равную ширину, и короткий текст будет иметь огромные отступы. С Flexible каждая кнопка займёт ширину своего текста, а свободное пространство останется между ними или по краям.
Сценарий для Expanded: внутри Column расположены три карточки, которые должны занимать равную высоту независимо от содержимого. Expanded равномерно делит доступную высоту, и каждая карточка растягивается до одинакового размера. Flexible с loose здесь не подойдёт — маленькая карточка останется маленькой, нарушив единый дизайн.
Вывод: выбирайте Flexible, когда содержимое определяет размер элемента и пустое пространство вокруг него допустимо. Выбирайте Expanded, когда все элементы должны быть одинакового размера независимо от контента.
Пример 1 демонстрирует базовое использование Flexible с FlexFit.loose в Row.
Row(
children: [
Flexible(
fit: FlexFit.loose,
child: Container(
color: Colors.amber,
child: const Text('short'),
),
),
Flexible(
fit: FlexFit.loose,
child: Container(
color: Colors.cyan,
child: const Text('long text for demo'),
),
),
],
)
В примере оба Flexible с loose fit. Первый контейнер с коротким текстом занимает свою естественную ширину. Второй с длинным — свою. Row выделяет каждому по половине доступной ширины, но loose fit позволяет контейнерам быть меньше, и свободное пространство остаётся пустым между ними.
Пример 2 — сравнение Flexible с Tight и Loose fit в Column.
Column(
children: [
Flexible(
fit: FlexFit.tight,
child: const Card(
child: ListTile(
title: Text('Tight — stretched full height'),
),
),
),
const SizedBox(
height: 8,
),
Flexible(
fit: FlexFit.loose,
child: const Card(
child: ListTile(
title: Text('Loose — natural height'),
),
),
),
],
)
В Column первый Flexible (tight) растягивает Card на половину доступной высоты. Второй Flexible (loose) — Card занимает высоту по содержимому, а остаток пространства остаётся пустым. Визуально видно, как tight принудительно заполняет область, а loose сохраняет компактный размер.
Вывод: в production-коде Flexible с loose применяется для полей формы, кнопок и текстовых блоков. Flexible с tight — эквивалентен Expanded и используется для равномерного распределения.
Ошибка 1: использование Flexible вне Flex-контейнера. Как и Expanded, Flexible выбрасывает LayoutException, если расположен не внутри Row, Column или Flex. Решение: всегда проверяйте родительскую цепочку виджетов.
Ошибка 2: путаница между Flexible и Expanded. Разработчики часто используют Expanded, когда нужен Flexible с loose fit, и наоборот. Результат: Expanded растягивает элемент с коротким текстом, создавая большие пустые поля. Решение: если размер элемента может быть меньше выделенного — используйте Flexible(fit: FlexFit.loose).
Ошибка 3: нулевой flex-фактор. Flexible с flex=0 ведёт себя как негибкий виджет, что может быть неочевидно. Разработчик ожидает гибкого поведения, но элемент занимает минимальный размер. Решение: flex должен быть ≥1 для активного распределения пространства.
Ошибка 4: Flexible внутри ScrollView без ограничения размеров. Если Flexible находится в Column внутри SingleChildScrollView, scroll-контейнер даёт бесконечное пространство, и flex-распределение не работает. Решение: ограничивать размер scroll-контейнера через SizedBox или ConstrainedBox.
Вывод: Flexible требует такого же внимания к родительскому контексту, как Expanded. Разница только в fit — выбирайте его осознанно в зависимости от задачи.
Часто задаваемые вопросы
Expanded — это Flexible с фиксированным fit=tight. Разница в том, что Expanded имеет жёсткую привязку к заполнению всего доступного пространства, а Flexible может использовать loose fit для сохранения естественного размера ребёнка.
Для кнопок с разной длиной текста используйте Flexible(fit: FlexFit.loose). Каждая кнопка займёт ширину по содержимому, и между ними не будет диспропорции. Expanded сделает все кнопки одинаковой ширины.
Установите fit: FlexFit.tight — Flexible будет принуждать ребёнка заполнять всё выделенное пространство. В этом режиме Flexible полностью идентичен Expanded по поведению.
Да, если ребёнок больше выделенного пространства. Flexible передаёт constraints с максимальным размером, равным доле Flex-контейнера. Если ребёнок превышает этот размер, он сжимается в пределах constraints.
Да, Flexible работает с любым Flex-контейнером, включая Row, Column и базовый виджет Flex с произвольной direction. Flex позволяет задавать нестандартные направления через mainAxisAlignment и crossAxisAlignment.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также