more_than_wrap 1.0.1
more_than_wrap: ^1.0.1 copied to clipboard
A Flutter package that provides a custom Wrap widget with limited number of rows and optional overflow widget display.
โจ more_than_wrap #
Wrap with a row limit โ and a real +N more widget.
Flutter's Wrap will happily grow forever. more_than_wrap stops after maxLines and drops a real overflow child at the end of the last row โ a chip, a button, whatever you build.
Only children that fit are mounted. Hidden items never sit in the tree. The overflow indicator is built during layout, so the first frame already has the correct count. No flash. No jump.
๐ฏ Flutter 3.32+ required ยท Try the live demo โ
๐ Features #
| ๐ Limited rows | maxLines caps the height. Pass null and it behaves like a regular Wrap. |
| ๐ Real overflow widget | +3 more, a chip, a tappable button โ it's a normal child, not canvas text. |
| ๐ฆฅ Lazy inflate | Overflowed children are unmounted. .builder doesn't even construct them. |
| ๐ฌ Correct first frame | Overflow is measured and rebuilt in the same layout pass. |
| ๐งฉ Fits the indicator | If +N is too wide, more children hide until it fits. |
| ๐งญ Wrap-compatible packing | spacing, runSpacing, alignment, runAlignment, crossAxisAlignment, RTL, clipBehavior. |
Direction is always horizontal (Wrap with direction: Axis.horizontal). Vertical wrap is out of scope.
Perfect for tag clouds, filter chips, avatar stacks, compact label rows.
๐ Getting started #
dependencies:
more_than_wrap: ^1.0.0
flutter pub get
That's it. Import and wrap.
๐งช Usage #
๐ฆ Children list #
Widgets in children are created up front; only those that fit are mounted.
import 'package:flutter/material.dart';
import 'package:more_than_wrap/more_than_wrap.dart';
LimitedWrap(
maxLines: 2,
spacing: 8,
runSpacing: 4,
overflowWidgetBuilder: (context, count) {
return Chip(label: Text('+$count more'));
},
children: [
for (final tag in tags) Chip(label: Text(tag)),
],
)
๐๏ธ Builder (lazy children) #
itemBuilder runs only for indices that need to be measured or shown. Everything past the visible prefix is never built.
LimitedWrap.builder(
maxLines: 2,
spacing: 8,
runSpacing: 4,
itemCount: tags.length,
itemBuilder: (context, index) {
return Chip(label: Text(tags[index]));
},
overflowWidgetBuilder: (context, count) {
return ActionChip(
label: Text('+$count more'),
onPressed: () {
// Expand, open a sheet, navigate, โฆ
},
);
},
)
๐ก Rule of thumb:
.builderfor long / expensive lists. The list constructor when the set is small and already in memory.
๐ฅ Overflow widget #
overflowWidgetBuilder is optional. Without it, children that do not fit are unmounted and nothing is shown in their place.
When provided, it is a LimitedWrapOverflowBuilder:
typedef LimitedWrapOverflowBuilder = Widget Function(
BuildContext context,
int overflowCount,
);
- Called only when at least one child does not fit
overflowCountis always> 0- Not called at all when everything fits โ the overflow slot is not even mounted
- The returned widget is a normal child: layout, hit testing, animation โ all work
If the indicator is wider than the leftover space on the last row, LimitedWrap hides more children (and bumps the count) until it fits. If that empties the last row and the indicator fits on the previous one โ it moves there.
โพ๏ธ Unlimited rows #
Omit maxLines (or pass null) to wrap like a regular Wrap. You can also omit overflowWidgetBuilder โ overflowed children are simply not mounted.
LimitedWrap(
spacing: 8,
runSpacing: 4,
children: chips,
)
๐จ Alignment (same names as Wrap) #
LimitedWrap(
maxLines: 2,
alignment: WrapAlignment.end,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
textDirection: TextDirection.rtl,
verticalDirection: VerticalDirection.down,
clipBehavior: Clip.hardEdge,
overflowWidgetBuilder: (context, count) => Text('+$count'),
children: chips,
)
| Parameter | What it does |
|---|---|
spacing |
โ๏ธ Gap between children in a row |
runSpacing |
โ๏ธ Gap between rows |
alignment |
Main-axis packing inside a row (start, end, center, spaceBetween, โฆ) |
runAlignment |
Where the rows sit if there is extra height |
crossAxisAlignment |
Align children within a row (start, end, center) |
textDirection |
LTR / RTL โ defaults to ambient Directionality |
verticalDirection |
down (first row on top) or up |
clipBehavior |
Clip if content overflows the incoming constraints |
โ๏ธ How it works #
LimitedWrap is not a thin wrapper around Wrap. It's a RenderObjectWidget that inflates children during layout, in the spirit of slivers:
- ๐งฑ Children are created from the start of the list until
maxLinesis filled. - ๐ Remaining items stay unmounted (
overflowCount = itemCount - placedCount). - ๐ฐ If
overflowCount > 0andoverflowWidgetBuilderis set, an overflow slot is inserted and the builder runs insideperformLayout(same idea asLayoutBuilder). Without a builder, overflowed children are simply left unmounted. - ๐ If the overflow widget still doesn't fit, the last visible child is unmounted, the count goes up, and the indicator is laid out again โ still in that layout pass.
- ๐ Positions then follow
RenderWrappacking for a horizontal wrap.
Because the overflow widget is built in-layout, the first paint already has the correct +N. No one-frame flash of a wrong count. No hiding overflowed widgets with opacity or Offstage.
โ ๏ธ Intrinsic dimensions of the overflow slot are not supported (same limitation as
LayoutBuilder). Give the indicator a concrete size, or let it size itself from the wrap'smaxWidth.
๐ฎ Example #
The example app is a playground: tweak max lines, item width, and item count live. The web build is the live demo.
cd example
flutter run
๐ฌ Additional information #
- ๐ API reference
- ๐ Issue tracker
- ๐ป Source
Bug reports and PRs are very welcome. Need vertical wrap (direction: Axis.vertical) or intrinsic sizing of the overflow slot? Open an issue.
๐ License #
BSD 3-Clause. See LICENSE and BSD-3-Clause.
