Flexbox
中文文档 | English
A Flutter library for CSS Flexbox layout. This package provides widgets for creating layouts using the CSS Flexbox layout model, bringing the power and flexibility of flexbox to Flutter.
Live Demo
Try out the interactive demo: https://fluttercandies.github.io/flexbox_layout/
Screenshots
![]() |
![]() |
![]() |
![]() |
Features
- Flexbox Widget: Similar to Flutter's
Wrapbut with full flexbox layout support includingflex-grow,flex-shrink,justify-content,align-items, and more. - FlexItem Widget: Wrap children with flex item properties like
order,flexGrow,flexShrink,alignSelf, andflexBasisPercent. - SliverFlexbox: A sliver version for use in
CustomScrollViewwith item recycling and viewport-based rendering. - FlexboxList: A convenience widget similar to
GridViewbut with flexbox layout capabilities. - DynamicFlexbox: Dynamic flexbox layout that automatically adjusts item sizes based on aspect ratios.
- Masonry: Pinterest-style shortest-column layout via
MasonryFlexboxListandSliverMasonryFlexbox, with aspect-ratio, fixed-extent, and measured sizing per item. - DimensionResolver: Utilities for asynchronously resolving dimensions (e.g., image sizes).
- Item Entrance Animation: Built-in staggered entrance animation helpers for list/sliver item builders.
- Performance: Static children are laid out once per pass, sliver rows are cached and re-anchored across deep scroll jumps, and pinch-zoom layout is lazy (O(visible rows · log n) per frame).
Installation
Add this to your package's pubspec.yaml file:
dependencies:
flexbox_layout: ^3.0.0
Requires Flutter >= 3.32.0 and Dart >= 3.8.0.
Usage
Basic Flexbox
import 'package:flexbox_layout/flexbox_layout.dart';
Flexbox(
flexDirection: FlexDirection.row,
flexWrap: FlexWrap.wrap,
justifyContent: JustifyContent.spaceBetween,
alignItems: AlignItems.center,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
children: [
FlexItem(
flexGrow: 1,
child: Container(width: 100, height: 100, color: Colors.red),
),
FlexItem(
flexGrow: 2,
child: Container(width: 100, height: 100, color: Colors.blue),
),
FlexItem(
flexGrow: 1,
alignSelf: AlignSelf.flexEnd,
child: Container(width: 100, height: 100, color: Colors.green),
),
],
)
FlexboxList
FlexboxList.count(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: const EdgeInsets.all(8),
children: List.generate(
100,
(index) => Card(
child: Center(child: Text('Item $index')),
),
),
)
FlexboxList with Max Extent
FlexboxList.extent(
maxCrossAxisExtent: 200, // Maximum width per item
mainAxisSpacing: 8,
crossAxisSpacing: 8,
children: List.generate(
100,
(index) => Card(child: Center(child: Text('Item $index'))),
),
)
SliverFlexbox in CustomScrollView
CustomScrollView(
slivers: [
SliverAppBar(title: Text('Flexbox')),
SliverFlexbox(
delegate: SliverChildBuilderDelegate(
(context, index) => Card(child: Text('Item $index')),
childCount: 100,
),
flexboxDelegate: SliverFlexboxDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
),
],
)
DynamicFlexboxList for Image Galleries
Just works! Images are automatically measured and arranged to fill each row.
DynamicFlexboxList(
targetRowHeight: 200,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
itemBuilder: (context, index) {
return Image.network(images[index].url, fit: BoxFit.cover);
},
itemCount: images.length,
)
SliverDynamicFlexbox with Aspect Ratios
CustomScrollView(
slivers: [
SliverDynamicFlexbox(
flexboxDelegate: SliverDynamicFlexboxDelegate(
targetRowHeight: 200,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
),
childDelegate: SliverChildBuilderDelegate(
(context, index) => Image.network(images[index].url),
childCount: images.length,
),
),
],
)
Masonry with MasonryFlexboxList
Unlike the row-packing galleries above (DynamicFlexboxList fills each row and aligns its edges), masonry packs each item into the currently shortest column (ties broken leftmost), so columns end up with different heights — the classic Pinterest look.
MasonryFlexboxList.count(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: const EdgeInsets.all(8),
childAspectRatioBuilder: (index) => photos[index].aspectRatio,
itemBuilder: (context, index) => Image.network(
photos[index].url,
fit: BoxFit.cover,
),
itemCount: photos.length,
)
MasonryFlexboxList.extent(maxCrossAxisExtent: ...) fits as many columns as possible instead of a fixed count, and SliverMasonryFlexbox is the raw sliver for use inside an existing CustomScrollView.
Three sizing modes — each item's main-axis extent resolves in order:
childAspectRatioBuilder(extent = column width / ratio) — use when the data knows intrinsic ratios (e.g. photos with known dimensions); placement is pure arithmetic.childMainAxisExtentBuilder(explicit pixels, given the resolved column width) — use for fixed-height cards or data-driven heights.- Measurement — items whose builders both return null are laid out with a tight-width / loose-height constraint and the observed extent is cached; use for free-form content like variable text cards.
The modes mix per item: return null from a builder for just the indices that need measuring.
Full-span items: fullSpanBuilder: (index) => ... marks items that span all columns — section headers, banners, or load-more footers. A full-span item sits below the tallest column, and all columns restart below it.
Performance: positions come from a checkpointed shortest-column placement cache, so steady scroll frames only touch attached children (each builder-sized child is laid out exactly once); deep jumps in the builder-sized modes never build the children in between; and measured-size changes are debounced and applied with a scroll-anchored correction so visible content does not jump.
Pinch-to-zoom masonry: SliverScalableMasonryFlexbox drives the column count from a FlexboxScaleController whose currentExtent is the desired max column width in pixels:
SliverScalableMasonryFlexbox(
controller: scaleController, // snapPoints: [120, 160, 200, 240, 280]
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatioBuilder: (index) => photos[index].aspectRatio,
delegate: SliverChildBuilderDelegate(
(context, index) => Image.network(photos[index].url, fit: BoxFit.cover),
childCount: photos.length,
),
)
Masonry columns are discrete, so the zoom re-columns in anchored flips: extent changes within one column-count band cost nothing (the same cached delegate instance is reused, no relayout), and each count flip is absorbed by a focal-point-anchored scroll correction so the content under the user's fingers stays put. Wire the gestures and NeverScrollableScrollPhysics exactly as in the scalable flexbox section below, and set the controller's snapPoints to your column-width thresholds for crisp snapping.
Using DimensionResolver for Pre-loading Image Sizes
class _GalleryState extends State<_Gallery> with DimensionResolverMixin {
final List<String> imageUrls = [...];
@override
void initState() {
super.initState();
// Pre-load all image dimensions
for (int i = 0; i < imageUrls.length; i++) {
resolveImageDimension(NetworkImage(imageUrls[i]), key: i);
}
}
@override
Widget build(BuildContext context) {
return SliverFlexboxDelegateWithDynamicAspectRatios(
childCount: imageUrls.length,
aspectRatioProvider: getAspectRatio, // From mixin
defaultAspectRatio: 1.0,
// ...
);
}
}
Using DynamicFlexItem for Problematic Widgets
When to use: If your child uses Stack with StackFit.expand or other widgets that don't handle unbounded constraints well during intrinsic size measurement.
SliverDynamicFlexbox(
flexboxDelegate: SliverDynamicFlexboxDelegate(...),
childDelegate: SliverChildBuilderDelegate(
(context, index) => DynamicFlexItem(
child: Stack(
fit: StackFit.expand,
children: [
Image.network(images[index], fit: BoxFit.cover),
Positioned(bottom: 8, left: 8, child: Text('Label')),
],
),
),
childCount: images.length,
),
)
Note: For simple Image widgets, you don't need DynamicFlexItem - use them directly.
Scalable Flexbox with Pinch-to-Zoom
Google Photos style pinch-to-zoom experience for your flexbox galleries, built on the smooth SliverFlexboxDelegateWithDirectExtent pipeline:
class _ScalableGalleryState extends State<_ScalableGallery>
with TickerProviderStateMixin {
late final controller = FlexboxScaleController(
initialExtent: 160.0,
minExtent: 60.0,
maxExtent: 280.0,
gridModeThreshold: 90.0, // 1:1 grid at small extents (zoomed in tight)
gridModeTransitionBand: 30.0, // morph continuously across this band
);
@override
void initState() {
super.initState();
controller.attachTickerProvider(this);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: controller.onScaleStart,
onScaleUpdate: controller.onScaleUpdate,
onScaleEnd: controller.onScaleEnd,
onDoubleTap: controller.onDoubleTap,
child: ListenableBuilder(
listenable: controller,
builder: (context, child) => CustomScrollView(
// Stop the scrollable from panning while a pinch is active.
physics: controller.isScaling
? const NeverScrollableScrollPhysics()
: null,
slivers: [
SliverScalableFlexbox(
controller: controller,
aspectRatios: photoRatios, // reuse the same list across builds
mainAxisSpacing: 2.0,
crossAxisSpacing: 2.0,
delegate: SliverChildBuilderDelegate(
(context, index) =>
Image.network(images[index].url, fit: BoxFit.cover),
childCount: images.length,
),
),
],
),
),
);
}
}
Key features:
- Continuous scaling: Item sizes track the pinch directly — no jumps between column counts
- Grid morphing:
gridBlendcontinuously morphs between natural aspect ratios and the uniform 1:1 grid acrossgridModeThreshold/gridModeTransitionBand - Focal-point anchoring: Content zooms around the user's fingers (
FlexboxScaleController.focalPointdrivesSliverFlexbox.anchorOffsetFraction) - Smooth snap animation: Snaps to predefined points with spring physics
- Velocity-based momentum: Continues motion based on gesture velocity
Notes:
- A plain
GestureDetectorcompetes with the scrollable's drag recognizer, so disable scrolling while a pinch is active by swapping inNeverScrollableScrollPhysicswhencontroller.isScaling(as above). For production-grade gesture arbitration, see the example app'sScaleScrollCoordinatorpattern. - When passing
aspectRatios, reuse the same list instance across rebuilds — layout prefix sums are cached per list instance, which keeps every pinch frame cheap. Create a fresh list only when the underlying data changes. WithoutaspectRatios, every child useschildAspectRatio(default 1.0) and the delegate must report a finite child count.
Item Entrance Animations
Staggered fade/slide/scale entrances for list items, with one-time deduplication so items animate only on first appearance. Works with every list component in this package: wrap any IndexedWidgetBuilder-style item builder — FlexboxList.builder, DynamicFlexboxList, MasonryFlexboxList, or the SliverChildBuilderDelegate of SliverFlexbox / SliverDynamicFlexbox / SliverMasonryFlexbox and their scalable variants.
class _FeedPageState extends State<FeedPage> {
// Auto mode: every unseen item animates once as it first appears.
// (Use FlexboxItemAnimationController.manual() to animate only
// explicitly enqueued index ranges, e.g. after a load-more batch.)
final _itemAnimation = FlexboxItemAnimationController.auto();
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverMasonryFlexbox(
masonryDelegate: SliverMasonryFlexboxDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
delegate: SliverChildBuilderDelegate(
withFlexboxItemAnimation(
controller: _itemAnimation,
// REQUIRED for lists that mutate anywhere but the tail
// (prepend/insert/remove/reorder): stable, data-derived ids.
animationIdBuilder: (index) => posts[index].id,
itemBuilder: (context, index) => PostCard(post: posts[index]),
),
childCount: posts.length,
),
),
],
);
}
}
Controller modes:
FlexboxItemAnimationController.auto()— every unseen item animates once. Items attached in the same frame (initial screen, deep jumps) cascade with a per-batch stagger; items attached by later frames (steady scroll-in) animate immediately.FlexboxItemAnimationController.manual()— only explicitly queued indexes animate (enqueueRange/enqueueInitialItems), each queued range staggering from its own start.
Stable animation ids: the default id is the item index, which is only safe for append-only lists. If your list can prepend, insert, remove, or reorder, supply stable data-derived ids via animationIdBuilder (or FlexboxItemTransition.animationId) — otherwise shifted items replay their entrance and new items get wrongly deduplicated.
Customization: tune FlexboxItemAnimationConfig (duration, stagger delay/cap, curve, slide distance, begin scale/opacity), or pass a transitionBuilder for fully custom effects (blur, rotation, multi-stage intervals via FlexboxItemTransitionValues). FlexboxItemAnimator bundles controller + config + builders into a single object for smaller call sites.
For children-list constructors (e.g. FlexboxList(children: ...)), prefer the builder variant, or wrap individual children with FlexboxItemTransition directly.
API Reference
Enums
| Enum | Values | Description |
|---|---|---|
FlexDirection |
row, rowReverse, column, columnReverse |
Direction of the main axis |
FlexWrap |
noWrap, wrap, wrapReverse |
Whether items wrap to multiple lines |
JustifyContent |
flexStart, flexEnd, center, spaceBetween, spaceAround, spaceEvenly |
Alignment along the main axis |
AlignItems |
flexStart, flexEnd, center, baseline, stretch |
Alignment along the cross axis |
AlignSelf |
auto, flexStart, flexEnd, center, baseline, stretch |
Override parent's AlignItems |
AlignContent |
flexStart, flexEnd, center, spaceBetween, spaceAround, stretch |
Alignment of flex lines |
FlexboxScaleMode |
grid1x1, aspectRatio |
Display mode for scalable flexbox |
FlexItem Properties
| Property | Type | Default | Description |
|---|---|---|---|
order |
int |
1 |
Order of the item in the flex container |
flexGrow |
double |
0.0 |
How much the item should grow relative to others |
flexShrink |
double |
1.0 |
How much the item should shrink relative to others |
alignSelf |
AlignSelf |
auto |
Cross-axis alignment override |
flexBasisPercent |
double |
-1.0 |
Initial size as a percentage (0.0-1.0) of parent |
minWidth |
double? |
null |
Minimum width constraint |
minHeight |
double? |
null |
Minimum height constraint |
maxWidth |
double? |
null |
Maximum width constraint |
maxHeight |
double? |
null |
Maximum height constraint |
wrapBefore |
bool |
false |
Force a wrap before this item |
Available Widgets
| Widget | Description |
|---|---|
Flexbox |
Basic flexbox layout widget (non-scrolling) |
FlexItem |
Wrapper for children with flex properties |
FlexboxList |
Scrollable list with flexbox layout |
SliverFlexbox |
Sliver version for CustomScrollView |
DynamicFlexboxList |
Auto-sizing flexbox list (measures children) |
SliverDynamicFlexbox |
Dynamic sliver for CustomScrollView |
MasonryFlexboxList |
Scrollable Pinterest-style masonry list (shortest-column packing) |
SliverMasonryFlexbox |
Masonry sliver for CustomScrollView |
DynamicFlexItem |
Wrapper for children with unbounded constraint issues |
SliverScalableFlexbox |
Scalable sliver with pinch-to-zoom support (Google Photos style) |
SliverScalableMasonryFlexbox |
Pinch-to-zoom masonry sliver (discrete column counts with anchored flips) |
Dimension Resolution Utilities
| Class/Type | Description |
|---|---|
DimensionResolver |
Async dimension resolver for images and custom content |
DimensionResolverMixin |
Mixin for easy dimension resolution in State classes |
BatchDimensionResolver |
Batch dimension resolution with progress tracking |
ItemDimension |
Immutable class representing width/height with aspect ratio support |
ImageProviderDimensionExtension |
Extension to get dimensions from ImageProvider |
Scale Controller
| Class/Type | Description |
|---|---|
FlexboxScaleController |
Controller for scalable flexbox with pinch-to-zoom (Google Photos style) |
FlexboxScaleMode |
Display mode enum (grid1x1, aspectRatio) |
Available Delegates
| Delegate | Description |
|---|---|
SliverFlexboxDelegateWithFixedCrossAxisCount |
Grid-like layout with fixed items per row/column |
SliverFlexboxDelegateWithMaxCrossAxisExtent |
Responsive layout with max item size |
SliverFlexboxDelegateWithAspectRatios |
Variable aspect ratios (pre-known) |
SliverFlexboxDelegateWithDynamicAspectRatios |
Dynamic aspect ratios via callback |
SliverFlexboxDelegateWithFlexValues |
Custom flex grow values per item |
SliverFlexboxDelegateWithBuilder |
Fully customizable via builder callback |
SliverFlexboxDelegateWithDirectExtent |
Direct extent control with lazy layout, smooth transitions and gridBlend 1:1 morphing (for pinch-to-zoom) |
SliverMasonryFlexboxDelegateWithFixedCrossAxisCount |
Masonry with a fixed number of columns |
SliverMasonryFlexboxDelegateWithMaxCrossAxisExtent |
Masonry with as many columns as fit a maximum width |
SliverFlexboxDelegateWithDirectExtentcaches layout prefix sums peraspectRatioslist instance: reuse the same list instance across frames and pass a freshly built list only when the data actually changes.
SliverDynamicFlexboxDelegate Options
| Property | Type | Default | Description |
|---|---|---|---|
targetRowHeight |
double |
200.0 |
Target height for each row |
mainAxisSpacing |
double |
0.0 |
Spacing between rows |
crossAxisSpacing |
double |
0.0 |
Spacing between items in a row |
minRowFillFactor |
double |
0.8 |
Min fill ratio for last row (0.0-1.0) |
defaultAspectRatio |
double |
1.0 |
Fallback aspect ratio when not measured |
debounceDuration |
Duration |
150ms |
Delay before applying size updates |
aspectRatioChangeThreshold |
double |
0.01 |
Min change (1%) to trigger layout update |
crossAxisExtentChangeThreshold |
double |
1.0 |
Min viewport width change to clear cache |
maxAspectRatioChecksPerLayout |
int |
8 |
Max intrinsic re-checks per layout pass |
aspectRatioCheckInterval |
int |
12 |
Min layout passes between re-checks per item |
aspectRatioGetter |
function? |
null |
Optional callback to provide aspect ratios |
lastChildLayoutTypeBuilder |
function? |
null |
Marks indices (e.g. a load-more footer) that get a dedicated full cross-axis row |
Architecture
Component Hierarchy
Flexbox (RenderObjectWidget)
├── RenderFlexbox (RenderObject)
│ └── FlexboxParentData
│ └── FlexItemData
│
FlexItem (ParentDataWidget)
└── applies FlexItemData to children
FlexboxList (StatelessWidget)
└── CustomScrollView
└── SliverFlexbox
└── RenderSliverFlexbox
└── SliverFlexboxParentData
DynamicFlexboxList (StatelessWidget)
└── CustomScrollView
└── SliverDynamicFlexbox
└── RenderSliverDynamicFlexbox
└── SliverDynamicFlexboxParentData
Layout Algorithm
- Measure children and determine their intrinsic sizes
- Group into flex lines based on flexWrap and available space
- Distribute free space using flexGrow/flexShrink ratios
- Apply alignment (justify-content, align-items, align-content)
- Position children with proper offsets
Example
See the example directory for a complete sample app demonstrating all features:
- Basic Flexbox usage
- FlexboxList with fixed cross axis count
- DynamicFlexbox with aspect ratio based layout
- Network Image Gallery
- Interactive Playground
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2026 iota9star
Credits
This library is inspired by CSS Flexbox layout and Google's flexbox-layout for Android.
Libraries
- flexbox_layout
- A Flutter library for flexbox layout.



