flutter_pan_select 1.0.0 copy "flutter_pan_select: ^1.0.0" to clipboard
flutter_pan_select: ^1.0.0 copied to clipboard

Multi-select with drag-to-select (pan) gesture for grids and lists. Lightweight, themeable selection controller with marquee selection and auto-scroll support.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_pan_select/flutter_pan_select.dart';

import 'widgets/selection_toolbar.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'flutter_pan_select demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const DemoHome(),
    );
  }
}

/// Demonstrates two usage styles: the grid uses an implicit controller via
/// `SelectionScope`, while the list uses an explicit `SelectionController`. The
/// top-right AppBar icon toggles the mode; page state is not preserved across
/// toggles (re-entering resets to the initial items).
class DemoHome extends StatefulWidget {
  const DemoHome({super.key});

  @override
  State<DemoHome> createState() => _DemoHomeState();
}

class _DemoHomeState extends State<DemoHome> {
  int _currentIndex = 0;
  int _nonce = 0;

  void _toggleView() {
    setState(() => _currentIndex = 1 - _currentIndex);
  }

  void _refresh() {
    setState(() => _nonce++);
  }

  @override
  Widget build(BuildContext context) {
    final isGrid = _currentIndex == 0;
    return Scaffold(
      appBar: AppBar(
        title: Text(isGrid ? 'Grid (overlay)' : 'List (left)'),
        actions: [
          IconButton(
            tooltip: 'Refresh',
            icon: const Icon(Icons.refresh),
            onPressed: _refresh,
          ),
          IconButton(
            tooltip: isGrid ? 'Switch to list' : 'Switch to grid',
            icon: Icon(
              isGrid ? Icons.view_list_outlined : Icons.grid_view_outlined,
            ),
            onPressed: _toggleView,
          ),
        ],
      ),
      body: isGrid
          ? GridDemo(key: ValueKey('grid-$_nonce'))
          : ListDemo(key: ValueKey('list-$_nonce')),
    );
  }
}

/// Grid + default SelectionItemOverlay + SelectionScope (implicit controller via scope).
class GridDemo extends StatefulWidget {
  const GridDemo({super.key});

  @override
  State<GridDemo> createState() => _GridDemoState();
}

class _GridDemoState extends State<GridDemo> {
  final List<int> _items = List.generate(50, (i) => i);

  @override
  Widget build(BuildContext context) {
    return SelectionScope<int>(
      child: Stack(
        children: [
          CustomScrollView(
            slivers: [
              SliverPadding(
                padding: const EdgeInsets.fromLTRB(12, 12, 12, 80),
                sliver: SliverPannableGrid<int>.builder(
                  getItemFromIndex: (index) => _items[index],
                  itemCount: _items.length,
                  crossAxisCount: 3,
                  crossAxisSpacing: 8,
                  mainAxisSpacing: 8,
                  itemBuilder: (context, index) {
                    return Container(
                      color: Colors.indigo.shade100,
                      alignment: Alignment.center,
                      child: Text('${_items[index]}'),
                    );
                  },
                ),
              ),
            ],
          ),
          Positioned(
            top: 8,
            right: 8,
            child: SelectionButton<int>(allItems: _items.toSet()),
          ),
          Positioned(
            left: 0,
            right: 0,
            bottom: 0,
            child: SelectionToolbar<int>(
              actions: [
                SelectionToolbarAction<int>(
                  icon: const Icon(Icons.delete_outline),
                  label: 'Delete',
                  onPressed: (selected, controller) {
                    final toRemove = selected.toSet();
                    setState(() {
                      _items.removeWhere(toRemove.contains);
                    });
                    controller.endSelection();
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

/// List + default SelectionLeftCheckbox + explicit controller (no SelectionScope).
class ListDemo extends StatefulWidget {
  const ListDemo({super.key});

  @override
  State<ListDemo> createState() => _ListDemoState();
}

class _ListDemoState extends State<ListDemo> {
  final SelectionController<String> _controller = SelectionController<String>();
  final List<String> _items = List.generate(30, (i) => 'Item ${i + 1}');

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        CustomScrollView(
          slivers: [
            SliverPadding(
              padding: const EdgeInsets.only(bottom: 80),
              sliver: SliverPannableList<String>.separated(
                controller: _controller,
                getItemFromIndex: (index) => _items[index],
                itemCount: _items.length,
                separatorBuilder: (context, index) => const Divider(height: 1),
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(_items[index]),
                    minTileHeight: 72,
                  );
                },
              ),
            ),
          ],
        ),
        Positioned(
          top: 8,
          right: 8,
          child: SelectionButton<String>(
            controller: _controller,
            allItems: _items.toSet(),
          ),
        ),
        Positioned(
          left: 0,
          right: 0,
          bottom: 0,
          child: SelectionToolbar<String>(
            controller: _controller,
            actions: [
              SelectionToolbarAction<String>(
                icon: const Icon(Icons.delete_outline),
                label: 'Delete',
                onPressed: (selected, controller) {
                  final toRemove = selected.toSet();
                  setState(() {
                    _items.removeWhere(toRemove.contains);
                  });
                  controller.endSelection();
                },
              ),
            ],
          ),
        ),
      ],
    );
  }
}
2
likes
160
points
36
downloads

Documentation

API reference

Publisher

verified publishersstingo.com

Weekly Downloads

Multi-select with drag-to-select (pan) gesture for grids and lists. Lightweight, themeable selection controller with marquee selection and auto-scroll support.

Repository (GitHub)
View/report issues

Topics

#selection #drag #gesture #grid #list

License

MIT (license)

Dependencies

flutter, sliver_tools

More

Packages that depend on flutter_pan_select