lm_smooth 0.1.3 copy "lm_smooth: ^0.1.3" to clipboard
lm_smooth: ^0.1.3 copied to clipboard

High-performance virtualized masonry grid for Flutter with known item extents, pinned sections, sessions, and drag reorder.

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.

lm_smooth example grid

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:

  • itemCount
  • itemBuilder
  • itemExtentBuilder via SmoothGrid.count
  • delegate for custom grid configuration
  • controller, physics, cacheExtent
  • reorderable, onReorder, reorderConfig
  • sessionController

SmoothSectionedGrid #

Grouped masonry grid with section headers.

  • sections
  • headerBuilder
  • itemBuilder
  • itemExtentBuilder
  • pinnedHeaders
  • pinnedHeaderExtent

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 itemExtentBuilder cheap 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 cacheExtent for your item complexity and target devices.

Current limitations #

  • Item extents must be known ahead of time.
  • Reorder is focused on vertical SmoothGrid.
  • SmoothSectionedGrid does not yet support cross-section reorder.
  • Horizontal masonry grid/reorder is not yet supported.
  • SmoothTable is 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.

0
likes
150
points
96
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

High-performance virtualized masonry grid for Flutter with known item extents, pinned sections, sessions, and drag reorder.

Repository (GitHub)
View/report issues

Topics

#grid #masonry #staggered #performance #reorder

License

MIT (license)

Dependencies

flutter

More

Packages that depend on lm_smooth