flexbox_layout 3.0.0 copy "flexbox_layout: ^3.0.0" to clipboard
flexbox_layout: ^3.0.0 copied to clipboard

CSS Flexbox layouts for Flutter: justified photo galleries, Pinterest-style masonry (waterfall) grids, and pinch-to-zoom scalable lists with high-performance sliver rendering.

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 Version Publisher Likes Pub Dev Live Demo

Live Demo #

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

Screenshots #

Basic Flexbox FlexboxList
Dynamic Gallery Sliver 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.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:

  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.

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: 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 _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

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 #

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 #

  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.

4
likes
160
points
107
downloads

Documentation

Documentation
API reference

Publisher

verified publisherfluttercandies.com

Weekly Downloads

CSS Flexbox layouts for Flutter: justified photo galleries, Pinterest-style masonry (waterfall) grids, and pinch-to-zoom scalable lists with high-performance sliver rendering.

Repository (GitHub)
View/report issues

Topics

#ui #widget #layout #flexbox #masonry

License

MIT (license)

Dependencies

extended_list_library, flutter

More

Packages that depend on flexbox_layout