Проблема
Приложению на Flutter, которое выходит на телефоны, планшеты и десктоп, недостаточно гибкой колонки. Экрану, который хорошо читается при ширине 390 логических пикселей, при 700 нужна центрированная карточка, а при 1200 — боковая панель. Flutter даёт измерения, но не даёт структуры, в которой их применять.
Поэтому проверки расползаются по коду. Здесь MediaQuery.of(context).size.width > 600, там LayoutBuilder, тремя виджетами ниже — тернарный оператор внутри Padding. Каждая по отдельности выглядит уместно, но вместе они образуют правила вёрстки, которые негде прочитать целиком.
Типичные проблемы:
- Значения брейкпойнтов дублируются в десятках виджетов и расходятся по мере изменения дизайна
LayoutBuilderсообщает ограничения родителя, тогда как нужен размер устройства- Вёрстка переключается на планшетный вариант, как только телефон поворачивают в альбомную ориентацию
- В дереве виджетов невозможно увидеть логику адаптивности, не прочитав каждый дочерний виджет
Как flutter_adaptive_layout решает это
AdaptiveLayout принимает по одному билдеру на размер экрана и child. Он измеряет устройство через MediaQuery, сопоставляет результат с ScreenSize и строит только подходящий вариант — при этом child создаётся один раз и передаётся в выбранный билдер.
AdaptiveLayout(
smallBuilder: (context, child) => child!,
mediumBuilder: (context, child) => Center(child: child),
largeBuilder: (context, child) => Row(
children: [const Sidebar(), Expanded(child: child!)],
),
child: const MyHomePage(),
)
Решение об адаптивности теперь живёт в одном виджете, на вершине поддерева, которым оно управляет, и записано как три варианта вёрстки, а не как цепочка условий.


Что умеет пакет
- Вёрстка по размеру экрана —
smallBuilder,mediumBuilderиlargeBuilder, каждый необязателен: если билдера нет, рисуетсяchild, поэтому можно описать только те размеры, которые важны - Свои брейкпойнты — по умолчанию 400 и 600 логических пикселей, переопределяются для отдельного виджета или для всего приложения
childстроится один раз — при смене вёрстки контент оборачивается, а не пересобирается с нуля- Устойчивость к повороту — классификация читает
MediaQuery.of(context).size.shortestSide, поэтому телефон остаётся маленьким экраном и в альбомной ориентации - Подключаемая логика классификации — реализуйте
ScreenSizeQualifier, чтобы делить экраны по ширине, платформе, состоянию складного экрана или как принято в вашей дизайн-системе - Без зависимостей — чистый Flutter поверх
MediaQuery, работает на iOS, Android, web, macOS, Windows и Linux
Как определяется размер экрана
BreakpointsQualifier по умолчанию сравнивает короткую сторону с двумя брейкпойнтами, причём включительно: значение, равное брейкпойнту, попадает в меньшую категорию.
| Размер экрана | Условие | Диапазон по умолчанию | Типичное устройство |
|---|---|---|---|
ScreenSize.small | shortestSide <= smallBreakpoint | 0–400 логических пикселей | Телефоны |
ScreenSize.medium | shortestSide <= mediumBreakpoint | 401–600 логических пикселей | Небольшие планшеты |
ScreenSize.large | больше mediumBreakpoint | 601+ логических пикселей | Большие планшеты, десктоп |
Брейкпойнты разрешаются в фиксированном порядке — побеждает первое заданное значение: аргументы BreakpointsQualifier, затем ближайший BreakpointsSetting выше по дереву, затем встроенные 400 и 600.
Сценарии использования
От телефона к планшету
Приложение, начинавшееся с телефонов, может сохранить все существующие экраны и добавить largeBuilder там, где широкая вёрстка действительно нужна. Экраны без билдера для нужного размера рисуют child без изменений, поэтому переход идёт экран за экраном, а не разом.
Навигация «список — детали»
Список, который на телефоне открывает детальный экран отдельным маршрутом, на планшете может показывать список и детали рядом. Оба варианта объявлены в одном виджете, так что разницу между ними видно прямо в диффе.
Дизайн-системы со своими брейкпойнтами
Оберните MaterialApp в BreakpointsSetting один раз — и каждый AdaptiveLayout в дереве возьмёт числа дизайн-системы. Позже поменять их — это правка одной строки, а не поиск по всему проекту.
Окна на web и десктопе
На web и десктопе измеряется размер окна, поэтому вёрстка реагирует на изменение его размера: тот же код, что обслуживает планшет, обслуживает и уменьшенное окно браузера.
С чего начать
Установите пакет:
flutter pub add flutter_adaptive_layout
Требуется Dart >=2.18.5 <4.0.0 и Flutter 1.17+.
Импортируйте его и оберните виджет, вёрстка которого должна адаптироваться:
import 'package:flutter_adaptive_layout/flutter_adaptive_layout.dart';
AdaptiveLayout(
largeBuilder: (context, child) => Center(
child: SizedBox(width: 600, child: child),
),
child: const ArticleView(), // на маленьких и средних экранах — как есть
)
Чтобы классифицировать экраны иначе, реализуйте ScreenSizeQualifier и передайте его в qualifier:
class WidthQualifier extends ScreenSizeQualifier {
@override
ScreenSize qualify(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width < 600) return ScreenSize.small;
if (width < 1024) return ScreenSize.medium;
return ScreenSize.large;
}
}
qualify вызывается при каждой перестройке, поэтому он должен быть дешёвым и без побочных эффектов.
Полноценное рабочее приложение лежит в каталоге example.