Fit Grid
Сетка, которая сама подбирает размер и количество плиток под доступное пространство, минимизируя пустые зазоры.
Описание
Пакет предоставляет виджет FitGrid, который сам подбирает количество столбцов и строк, размер плиток и их позиции — без ручного указания crossAxisCount, как в обычном GridView. Раскладка пересчитывается на layout-фазе (CustomMultiChildLayout + MultiChildLayoutDelegate), поэтому ресайз области не вызывает rebuild дерева виджетов — только relayout.
Основные возможности
- Автоматический подбор количества столбцов/строк и размера плиток под доступное пространство
- Минимизация пустого пространства: если элементов меньше вместимости страницы — приоритет отдаётся заполнению области, если страница заполнена целиком — максимизации размера плитки
- Расчёт раскладки на layout-фазе, без rebuild при ресайзе
- Настраиваемое выравнивание сетки через
AlignmentDirectional— учитывает направление текста (RTL/LTR) - Отдельное выравнивание неполной последней строки
- Поддержка отступов между плитками (
itemPadding) и от границ области (gridPadding) - Поддержка пропорций плиток (
aspectRatio) - Настраиваемый список пресетов размеров плиток (
presets) — свой набор форм-факторов вместо встроенного - Встроенная пагинация:
maxItemsPerPage,currentPageи уведомления черезpageInfoNotifier - Утилита расчёта
maxItemsPerPageпо ширине области с дефолтными брейкпоинтами под mobile/tablet и desktop/web - Без сторонних зависимостей — только Flutter SDK
Установка
Пакет опубликован на pub.dev. Добавьте зависимость в pubspec.yaml:
dependencies:
fit_grid: ^1.0.0
Затем выполните:
flutter pub get
Использование
Базовый пример
import 'package:fit_grid/fit_grid.dart';
FitGrid(
children: [
Card(child: Text('Карточка 1')),
Card(child: Text('Карточка 2')),
Card(child: Text('Карточка 3')),
// ... больше карточек
],
maxItemsPerPage: 9,
alignment: AlignmentDirectional.center,
)
Расширенный пример с настройками
FitGrid(
children: _buildCards(),
maxItemsPerPage: 12,
alignment: AlignmentDirectional.topStart,
itemPadding: Size(16, 16), // Отступы между плитками
gridPadding: EdgeInsets.all(24), // Отступы от границ
aspectRatio: 16 / 9, // Пропорции плитки (опционально)
)
Выравнивание и RTL
alignment типизирован как AlignmentDirectional, поэтому start/end автоматически зеркалятся под направление текста из ближайшего Directionality (например, для арабской/ивритской локали). Если нужно жёстко закрепить сторону независимо от locale — используйте AlignmentDirectional.topLeft/topRight и т.д. напрямую вместо topStart/topEnd.
Пример с пагинацией
final ValueNotifier<FitGridPageInfo> pageInfoNotifier = ValueNotifier<FitGridPageInfo>(
const FitGridPageInfo(currentPage: 1, totalPages: 1),
);
// Слушаем изменения информации о страницах
ValueListenableBuilder<FitGridPageInfo>(
valueListenable: pageInfoNotifier,
builder: (context, pageInfo, child) {
return Text('Страница ${pageInfo.currentPage} из ${pageInfo.totalPages}');
},
)
// Используем в FitGrid
FitGrid(
children: _buildCards(),
maxItemsPerPage: 12,
alignment: AlignmentDirectional.center,
pageInfoNotifier: pageInfoNotifier, // Уведомления о страницах
currentPage: 1, // Текущая страница (опционально)
)
Расчёт maxItemsPerPage по ширине (брейкпоинты)
Если вы хотите автоматически подбирать maxItemsPerPage в зависимости от ширины области,
можно использовать дефолтные брейкпоинты пакета:
LayoutBuilder(
builder: (context, constraints) {
final FitGridPlatformGroup platformGroup = FitGridPlatformGroup.desktopAndWeb;
final int maxItemsPerPage = FitGridItemsPerPageCalculator.calculateForPlatformGroup(
width: constraints.maxWidth,
platformGroup: platformGroup,
);
return FitGrid(
children: _buildCards(),
maxItemsPerPage: maxItemsPerPage,
alignment: AlignmentDirectional.center,
);
},
)
FitGridItemsPerPageBreakpoint
FitGridItemsPerPageBreakpoint — правило вида «диапазон ширины → рекомендуемое maxItemsPerPage».
minWidth: минимальная ширина (включительно)maxWidth: максимальная ширина (включительно), можно использоватьdouble.infinitymaxItemsPerPage: рекомендуемое значениеmaxItemsPerPageдля диапазона
Если дефолтные брейкпоинты вам не подходят, вы можете описать собственные:
LayoutBuilder(
builder: (context, constraints) {
final List<FitGridItemsPerPageBreakpoint> breakpoints =
<FitGridItemsPerPageBreakpoint>[
const FitGridItemsPerPageBreakpoint(
minWidth: 0,
maxWidth: 599,
maxItemsPerPage: 8,
),
const FitGridItemsPerPageBreakpoint(
minWidth: 600,
maxWidth: 1023,
maxItemsPerPage: 12,
),
const FitGridItemsPerPageBreakpoint(
minWidth: 1024,
maxWidth: double.infinity,
maxItemsPerPage: 16,
),
];
final int maxItemsPerPage = FitGridItemsPerPageCalculator.calculateForBreakpoints(
width: constraints.maxWidth,
breakpoints: breakpoints,
);
return FitGrid(
children: _buildCards(),
maxItemsPerPage: maxItemsPerPage,
alignment: AlignmentDirectional.center,
);
},
)
Пресеты размеров плиток
FitGrid перебирает список пресетов и выбирает тот, что даёт максимальное заполнение области (presets, по умолчанию — встроенный tileSizePresets). В debug-режиме выбранный пресет выводится в консоль.
Пресет — это не только пропорция, но и потолок абсолютного размера плитки: если элементов на странице ровно maxItemsPerPage (страница заполнена целиком), плитка не может превысить preset.size. Если встроенные пресеты вам не подходят (например, нужны более крупные плитки на широких экранах), передайте свой список:
FitGrid(
children: _buildCards(),
maxItemsPerPage: 9,
alignment: AlignmentDirectional.center,
presets: const [
TileSizePreset('wide', Size(900, 400)),
TileSizePreset('square', Size(500, 500)),
],
)
API
FitGrid
Основной виджет пакета.
Параметры
| Параметр | Тип | Обязательный | По умолчанию | Описание |
|---|---|---|---|---|
children |
List<Widget> |
✅ | - | Список виджетов-карточек для отображения |
maxItemsPerPage |
int |
✅ | - | Максимальное количество элементов на странице |
alignment |
AlignmentDirectional |
✅ | - | Выравнивание сетки внутри области (учитывает RTL/LTR) |
itemPadding |
Size |
❌ | Size(8, 8) |
Отступы между плитками (width = горизонтальный, height = вертикальный) |
gridPadding |
EdgeInsets |
❌ | EdgeInsets.zero |
Внешние отступы сетки от границ области |
aspectRatio |
double? |
❌ | null |
Пропорции плитки (width / height). Если null, используется 1:1 |
pageInfoNotifier |
ValueNotifier<FitGridPageInfo>? |
❌ | null |
ValueNotifier для уведомления о текущей странице и общем количестве страниц |
currentPage |
int? |
❌ | null |
Текущая страница (начинается с 1). Если не указан, используется значение из pageInfoNotifier.value.currentPage, по умолчанию 1 |
presets |
List<TileSizePreset> |
❌ | tileSizePresets |
Список пресетов размеров плитки, из которых выбирается оптимальный |
Выравнивание
Поддерживаются все стандартные варианты AlignmentDirectional:
AlignmentDirectional.topStart,topCenter,topEndAlignmentDirectional.centerStart,center,centerEndAlignmentDirectional.bottomStart,bottomCenter,bottomEnd
а также нейтральные к направлению варианты (topLeft/topRight и т.д.), если зеркалирование под RTL не нужно.
findOptimalPreset
Функция для автоматического выбора оптимального пресета размера плиток.
TileSizePreset findOptimalPreset({
required Size layoutSize,
required int itemCount,
required int maxItems,
required Size itemPadding,
required Alignment alignment,
required EdgeInsets gridPadding,
double? aspectRatio,
List<TileSizePreset> presets = tileSizePresets,
})
TileSizePreset
Класс для представления предустановленного размера плитки.
class TileSizePreset {
final String name; // Название пресета
final Size? size; // Размер плитки (null означает максимальный размер)
double? get aspectRatio; // Соотношение сторон
}
tileSizePresets
Константа со встроенным списком предустановленных размеров плиток (используется по умолчанию, если не передан свой presets):
377x200332x266432x366409x234351x304604x351615x309
FitGridItemsPerPageCalculator
Класс для расчёта количества элементов на странице по ширине области.
Методы
calculateForPlatformGroup- рассчитываетmaxItemsPerPageпо ширине области с использованием дефолтных брейкпоинтов для указанной группы платформcalculateForBreakpoints- рассчитываетmaxItemsPerPageпо ширине области на основе произвольного списка брейкпоинтов
FitGridDefaultBreakpoints
Класс с дефолтными правилами (брейкпоинтами) для расчёта количества элементов на странице.
Константы
mobileAndTabletBreakpoints- брейкпоинты для мобильных устройств и планшетовdesktopAndWebBreakpoints- брейкпоинты для десктопа и веба
FitGridItemsPerPageBreakpoint
Класс для представления правила брейкпоинта.
class FitGridItemsPerPageBreakpoint {
final double minWidth; // Минимальная ширина (включительно)
final double maxWidth; // Максимальная ширина (включительно), можно использовать double.infinity
final int maxItemsPerPage; // Рекомендуемое значение maxItemsPerPage для диапазона
}
FitGridPageInfo
Класс для хранения информации о текущей странице и общем количестве страниц.
class FitGridPageInfo {
final int currentPage; // Текущая страница (начинается с 1)
final int totalPages; // Общее количество страниц
}
FitGridLayoutDelegate
MultiChildLayoutDelegate, на котором построен FitGrid. Экспортируется для случаев, когда нужно встроить ту же раскладку в свой CustomMultiChildLayout напрямую.
TileLayoutCalculator
Калькулятор оптимальной сетки: перебирает варианты количества столбцов/строк, считает размер плитки с учётом пропорций, отступов и потолка пресета, а также смещение сетки для выравнивания. Экспортируется для кастомных сценариев расчёта вне FitGrid.
Примеры
Полный рабочий пример доступен в папке example/. Запустите его командой:
cd example
flutter run
Как это работает
-
Расчёт оптимальной сетки: алгоритм перебирает все возможные комбинации строк и столбцов для заданного количества элементов. Если элементов меньше
maxItemsPerPage, приоритет отдаётся заполнению области; если страница заполнена целиком — максимизируется площадь плитки (минимизируется пустое пространство). -
Выбор пресета: для каждого пресета из
presetsпрогоняется расчёт сетки, и выбирается тот, что даёт наибольший коэффициент заполнения области. Пресет задаёт как пропорцию плитки, так и потолок её абсолютного размера. -
Расчёт размера плиток: для выбранной конфигурации сетки и пресета рассчитывается финальный размер плитки с учётом доступного пространства, отступов и заданных пропорций (
aspectRatioпереопределяет пропорцию пресета). -
Позиционирование: плитки размещаются с учётом выбранного
AlignmentDirectional(резолвится вAlignmentчерезDirectionalityконтекста), включая отдельное выравнивание неполной последней строки. -
Пересчёт на layout-фазе: всё вышеперечисленное происходит в
FitGridLayoutDelegate.performLayout, а не вbuild(), поэтому ресайз области не перестраивает дерево виджетов — только relayout.
Тесты
Модульные и виджет-тесты покрывают алгоритм раскладки, брейкпоинты, подбор пресета, кастомные пресеты, RTL-выравнивание и пагинацию:
flutter test
Требования
- Flutter SDK:
>=3.41.9 - Dart SDK:
>=3.6.0 <4.0.0
Лицензия
MIT, см. LICENSE.