Skip to content

Repository files navigation

Flexbox

中文文档 | English

Flexbox Logo

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.

Pub VersionPublisherLikesPub DevLive Demo

Live Demo

Try out the interactive demo: https://fluttercandies.github.io/flexbox_layout/

Screenshots

Basic FlexboxFlexboxList
Dynamic GallerySliver Dynamic

Features

  • Flexbox Widget: Similar to Flutter's Wrap but with full flexbox layout support including flex-grow, flex-shrink, justify-content, align-items, and more.
  • FlexItem Widget: Wrap children with flex item properties like order, flexGrow, flexShrink, alignSelf, and flexBasisPercent.
  • SliverFlexbox: A sliver version for use in CustomScrollView with item recycling and viewport-based rendering.
  • FlexboxList: A convenience widget similar to GridView but with flexbox layout capabilities.
  • DynamicFlexbox: Dynamic flexbox layout that automatically adjusts item sizes based on aspect ratios.
  • Masonry: Pinterest-style shortest-column layout via MasonryFlexboxList and SliverMasonryFlexbox, 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.1.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:constEdgeInsets.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) {
returnImage.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:constEdgeInsets.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:

  1. childAspectRatioBuilder (extent = column width / ratio) — use when the data knows intrinsic ratios (e.g. photos with known dimensions); placement is pure arithmetic.
  2. childMainAxisExtentBuilder (explicit pixels, given the resolved column width) — use for fixed-height cards or data-driven heights.
  3. 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. Masonry items intentionally stay single-column; keeping placement on one shortest-column path avoids extra hot-path branching and keeps scroll-window queries predictable.

For best cache reuse, keep masonry sizing callbacks (childAspectRatioBuilder / childMainAxisExtentBuilder) stable across unrelated rebuilds, for example by passing a method tear-off or a stored function. Replace the callback instance only when the sizing data source changes; the render object treats sizing-callback identity as a placement input so stale masonry geometry is not kept after real data changes.

MasonryFlexboxList follows Flutter's BoxScrollView contract, so standard scroll-view parameters such as controller, primary, physics, padding, cacheExtent, restoration, keyboard dismissal, and clipping behave like ListView/GridView. Masonry and flexbox sliver delegates also share the ExtendedListDelegate hooks: collectGarbage, viewportBuilder, closeToTrailing, and lastChildLayoutTypeBuilder. lastChildLayoutTypeBuilder is a true last-child hook: only the final child can become a full cross-axis load-more / no-more footer, and non-final indexes are ignored. Masonry keeps regular items single-column on the shortest-column placement path; the footer is separate tail geometry, not an arbitrary cross-column span API. The packaged scalable flexbox and scalable masonry slivers forward the same hooks to their internal delegates.

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_GalleryStateextendsState<_Gallery> withDimensionResolverMixin {
finalList<String> imageUrls = [...];
@overridevoidinitState() {
super.initState();
// Pre-load all image dimensionsfor (int i =0; i < imageUrls.length; i++) {
resolveImageDimension(NetworkImage(imageUrls[i]), key: i);
}
}
@overrideWidgetbuild(BuildContext context) {
returnSliverFlexboxDelegateWithDynamicAspectRatios(
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_ScalableGalleryStateextendsState<_ScalableGallery>
withTickerProviderStateMixin {
latefinal 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
);
@overridevoidinitState() {
super.initState();
controller.attachTickerProvider(this);
}
@overridevoiddispose() {
controller.dispose();
super.dispose();
}
@overrideWidgetbuild(BuildContext context) {
returnGestureDetector(
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
?constNeverScrollableScrollPhysics()
: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: gridBlend continuously morphs between natural aspect ratios and the uniform 1:1 grid across gridModeThreshold/gridModeTransitionBand
  • Focal-point anchoring: Content zooms around the user's fingers (FlexboxScaleController.focalPoint drives SliverFlexbox.anchorOffsetFraction)
  • Smooth snap animation: Snaps to predefined points with spring physics
  • Velocity-based momentum: Continues motion based on gesture velocity

Notes:

  • A plain GestureDetector competes with the scrollable's drag recognizer, so disable scrolling while a pinch is active by swapping in NeverScrollableScrollPhysics when controller.isScaling (as above). For production-grade gesture arbitration, see the example app's ScaleScrollCoordinator pattern.
  • 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. Without aspectRatios, every child uses childAspectRatio (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_FeedPageStateextendsState<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();
@overrideWidgetbuild(BuildContext context) {
returnCustomScrollView(
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

EnumValuesDescription
FlexDirectionrow, rowReverse, column, columnReverseDirection of the main axis
FlexWrapnoWrap, wrap, wrapReverseWhether items wrap to multiple lines
JustifyContentflexStart, flexEnd, center, spaceBetween, spaceAround, spaceEvenlyAlignment along the main axis
AlignItemsflexStart, flexEnd, center, baseline, stretchAlignment along the cross axis
AlignSelfauto, flexStart, flexEnd, center, baseline, stretchOverride parent's AlignItems
AlignContentflexStart, flexEnd, center, spaceBetween, spaceAround, stretchAlignment of flex lines
FlexboxScaleModegrid1x1, aspectRatioDisplay mode for scalable flexbox

FlexItem Properties

PropertyTypeDefaultDescription
orderint1Order of the item in the flex container
flexGrowdouble0.0How much the item should grow relative to others
flexShrinkdouble1.0How much the item should shrink relative to others
alignSelfAlignSelfautoCross-axis alignment override
flexBasisPercentdouble-1.0Initial size as a percentage (0.0-1.0) of parent
minWidthdouble?nullMinimum width constraint
minHeightdouble?nullMinimum height constraint
maxWidthdouble?nullMaximum width constraint
maxHeightdouble?nullMaximum height constraint
wrapBeforeboolfalseForce a wrap before this item

Available Widgets

WidgetDescription
FlexboxBasic flexbox layout widget (non-scrolling)
FlexItemWrapper for children with flex properties
FlexboxListScrollable list with flexbox layout
SliverFlexboxSliver version for CustomScrollView
DynamicFlexboxListAuto-sizing flexbox list (measures children)
SliverDynamicFlexboxDynamic sliver for CustomScrollView
MasonryFlexboxListScrollable Pinterest-style masonry list (shortest-column packing)
SliverMasonryFlexboxMasonry sliver for CustomScrollView
DynamicFlexItemWrapper for children with unbounded constraint issues
SliverScalableFlexboxScalable sliver with pinch-to-zoom support (Google Photos style)
SliverScalableMasonryFlexboxPinch-to-zoom masonry sliver (discrete column counts with anchored flips)

Dimension Resolution Utilities

Class/TypeDescription
DimensionResolverAsync dimension resolver for images and custom content
DimensionResolverMixinMixin for easy dimension resolution in State classes
BatchDimensionResolverBatch dimension resolution with progress tracking
ItemDimensionImmutable class representing width/height with aspect ratio support
ImageProviderDimensionExtensionExtension to get dimensions from ImageProvider

Scale Controller

Class/TypeDescription
FlexboxScaleControllerController for scalable flexbox with pinch-to-zoom (Google Photos style)
FlexboxScaleModeDisplay mode enum (grid1x1, aspectRatio)

Available Delegates

DelegateDescription
SliverFlexboxDelegateWithFixedCrossAxisCountGrid-like layout with fixed items per row/column
SliverFlexboxDelegateWithMaxCrossAxisExtentResponsive layout with max item size
SliverFlexboxDelegateWithAspectRatiosVariable aspect ratios (pre-known)
SliverFlexboxDelegateWithDynamicAspectRatiosDynamic aspect ratios via callback
SliverFlexboxDelegateWithFlexValuesCustom flex grow values per item
SliverFlexboxDelegateWithBuilderFully customizable via builder callback
SliverFlexboxDelegateWithDirectExtentDirect extent control with lazy layout, smooth transitions and gridBlend 1:1 morphing (for pinch-to-zoom)
SliverMasonryFlexboxDelegateWithFixedCrossAxisCountMasonry with a fixed number of columns
SliverMasonryFlexboxDelegateWithMaxCrossAxisExtentMasonry with as many columns as fit a maximum width

SliverFlexboxDelegateWithDirectExtent caches layout prefix sums per aspectRatios list instance: reuse the same list instance across frames and pass a freshly built list only when the data actually changes.

SliverDynamicFlexboxDelegate Options

PropertyTypeDefaultDescription
targetRowHeightdouble200.0Target height for each row
mainAxisSpacingdouble0.0Spacing between rows
crossAxisSpacingdouble0.0Spacing between items in a row
minRowFillFactordouble0.8Min fill ratio for last row (0.0-1.0)
defaultAspectRatiodouble1.0Fallback aspect ratio when not measured
debounceDurationDuration150msDelay before applying size updates
aspectRatioChangeThresholddouble0.01Min change (1%) to trigger layout update
crossAxisExtentChangeThresholddouble1.0Min viewport width change to clear cache
maxAspectRatioChecksPerLayoutint8Max intrinsic re-checks per layout pass
aspectRatioCheckIntervalint12Min layout passes between re-checks per item
aspectRatioGetterfunction?nullOptional callback to provide aspect ratios
lastChildLayoutTypeBuilderfunction?nullMarks the final child as a dedicated full cross-axis footer/load-more 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

  1. Measure children and determine their intrinsic sizes
  2. Group into flex lines based on flexWrap and available space
  3. Distribute free space using flexGrow/flexShrink ratios
  4. Apply alignment (justify-content, align-items, align-content)
  5. 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.

About

A Flutter library for CSS Flexbox layout.

Topics

Resources

Stars

18 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages