lm_smooth
High-performance virtualized masonry views for Flutter.
lm_smooth is built for feeds, dashboards, and device grids where item heights are known ahead of time. It avoids runtime child measurement, precomputes item geometry, and keeps scrolling predictable for large datasets.

Features
- Fixed-column masonry grid with uneven item heights
- Lazy item building with a custom sliver render pipeline
- Precomputed layout cache and spatial index for fast viewport queries
- Optional isolate layout computation for large item counts
- Long-press drag reorder with preview animation and edge auto-scroll
- Sectioned grids with in-scroll or pinned headers
- Scroll-state sessions for tabs/pages that need restore behavior
- Known-extent vertical and horizontal lists
- Basic virtualized table with pinned rows and columns
When to use it
Use lm_smooth when:
- item heights are available from your model or can be computed cheaply
- you need a masonry feed with many items
- you want built-in drag reorder for a vertical masonry grid
- you need section headers, pinned headers, or scroll-state restore
- you want predictable scroll performance over runtime measurement flexibility
This package is not a runtime measurement solution. It works best when item height is known before layout.
Install
dependencies:
lm_smooth: ^0.1.2
import 'package:lm_smooth/lm_smooth.dart';
Quick start
class DemoPage extends StatelessWidget {
DemoPage({super.key});
final items = List.generate(1000, (index) => index);
double heightForItem(int item) => 100 + (item % 5) * 24.0;
@override
Widget build(BuildContext context) {
return SmoothGrid.count(
itemCount: items.length,
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: const EdgeInsets.all(8),
itemExtentBuilder: (index) => heightForItem(items[index]),
itemBuilder: (context, index) {
final item = items[index];
return SmoothGridTile(
child: Card(
child: Center(child: Text('Item $item')),
),
);
},
);
}
}
Using builder-based grids
For best performance, use itemCount, itemBuilder, and itemExtentBuilder instead of building a full list of child widgets up front. SmoothGrid keeps scrolling predictable by requiring the extent of each item before layout.
Example:
SmoothGrid.count(
itemCount: devices.length,
crossAxisCount: crossAxisCount,
crossAxisSpacing: AppDimension.verticalAxisSpacingCard,
mainAxisSpacing: AppDimension.horizontalAxisSpacingCard,
itemExtentBuilder: (index) => deviceCardHeight(devices[index]),
itemBuilder: (context, index) {
final device = devices[index];
return SmoothGridTile(
key: ValueKey(device.id),
child: DevicesGroupsItem(
hasBlur: true,
device: device,
),
);
},
)
Builder-based usage preserves lazy construction and is the recommended path for large collections.
Drag reorder
class ReorderDemo extends StatefulWidget {
const ReorderDemo({super.key});
@override
State<ReorderDemo> createState() => _ReorderDemoState();
}
class _ReorderDemoState extends State<ReorderDemo> {
final items = List.generate(200, (index) => index);
double heightForItem(int item) => 80 + (item % 6) * 20.0;
@override
Widget build(BuildContext context) {
return SmoothGrid.count(
itemCount: items.length,
reorderable: true,
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: const EdgeInsets.all(8),
itemExtentBuilder: (index) => heightForItem(items[index]),
itemBuilder: (context, index) {
final item = items[index];
return SmoothGridTile(
key: ValueKey(item),
child: Card(child: Center(child: Text('Item $item'))),
);
},
onReorder: (oldIndex, newIndex) {
setState(() {
final item = items.removeAt(oldIndex);
final insertAt = newIndex > oldIndex ? newIndex - 1 : newIndex;
items.insert(insertAt, item);
});
},
);
}
}
Use stable keys when reordering stateful children.
Sectioned grid
SmoothSectionedGrid renders multiple masonry sections in one scroll view. Headers can scroll normally or remain pinned.
SmoothSectionedGrid(
sections: const [
SmoothGridSection(id: 'today', itemCount: 40),
SmoothGridSection(id: 'archive', itemCount: 80),
],
pinnedHeaders: true,
pinnedHeaderExtent: 56,
crossAxisCount: 2,
headerBuilder: (context, sectionIndex) => Text('Section $sectionIndex'),
itemExtentBuilder: (sectionIndex, itemIndex) => 120,
itemBuilder: (context, sectionIndex, itemIndex) {
return SmoothGridTile(child: Text('$sectionIndex / $itemIndex'));
},
)
Sessions
Use SmoothSessionController when a view needs to restore scroll offset after switching tabs/pages or rebuilding the route.
final session = SmoothSessionController(id: 'devices');
SmoothGrid.count(
sessionController: session,
itemCount: devices.length,
crossAxisCount: 2,
itemExtentBuilder: (index) => deviceCardHeight(devices[index]),
itemBuilder: (context, index) => DeviceCard(device: devices[index]),
)
Dispose the controller when the owning widget is disposed.
API overview
SmoothGrid
Primary masonry grid widget.
Common parameters:
itemCountitemBuilderitemExtentBuilderviaSmoothGrid.countdelegatefor custom grid configurationcontroller,physics,cacheExtentreorderable,onReorder,reorderConfigsessionController
SmoothSectionedGrid
Grouped masonry grid with section headers.
sectionsheaderBuilderitemBuilderitemExtentBuilderpinnedHeaderspinnedHeaderExtent
SmoothList
Known-extent ListView convenience wrapper with vertical and horizontal support.
SmoothTable
Early data-grid style widget for large row/column datasets. It supports vertical row virtualization, horizontal cell culling, and pinned rows/columns.
Performance notes
- Keep
itemExtentBuildercheap and deterministic. - Precompute heights from model data when possible.
- Do not measure widgets inside
itemExtentBuilder. - Prefer builder APIs over prebuilt child lists.
- Use stable keys for reorderable items.
- Tune
cacheExtentfor your item complexity and target devices.
Current limitations
- Item extents must be known ahead of time.
- Reorder is focused on vertical
SmoothGrid. SmoothSectionedGriddoes not yet support cross-section reorder.- Horizontal masonry grid/reorder is not yet supported.
SmoothTableis intentionally small and focused; it is not a full spreadsheet component.
Example app
The example app contains focused screens for:
- large masonry grid and reorder
- pinned section headers
- horizontal known-extent list
- vertical known-extent list
- pinned table rows/columns
Run it with:
cd example
flutter run
Benchmarks
Benchmarks live in benchmark/ and can be run with:
flutter test benchmark/layout_benchmark_test.dart --reporter expanded
License
MIT. See LICENSE.
Libraries
- lm_smooth
- High-performance virtualized masonry views for Flutter.