selection_mode 0.0.6 copy "selection_mode: ^0.0.6" to clipboard
selection_mode: ^0.0.6 copied to clipboard

A Flutter package for multi-item selection with range selection

Selection Mode #

pub package

A Flutter package for multi-item selection with drag, range selection.

Features #

  • Drag selection - Touch and drag between items for range selection
  • Auto-scroll - Smooth scrolling during drag selection
  • Stable selection - Uses ValueKey identifiers for persistent selection across data changes
  • Flexible behaviors - Manual, auto-enable, or auto-toggle modes
  • Selection constraints - Limit maximum selections
  • Haptic feedback - Configurable haptic responses

Quick Start #

class PhotoGrid extends StatefulWidget {
  @override
  State<PhotoGrid> createState() => _PhotoGridState();
}

class _PhotoGridState extends State<PhotoGrid> {
  final _controller = SelectionModeController();
  final _scrollController = ScrollController();
  final List<Photo> photos = [];

  @override
  Widget build(BuildContext context) {
    return SelectionMode(
      controller: _controller,
      scrollController: _scrollController, // For auto-scroll during drag
      options: SelectionOptions(
        behavior: SelectionBehavior.autoEnable,
        dragSelection: DragSelectionOptions(),
      ),
      child: Scaffold(
        appBar: MaterialSelectionAppBar(
          actions: [
            IconButton(
              icon: const Icon(Icons.share),
              onPressed: () => _shareSelected(),
            ),
            IconButton(
              icon: const Icon(Icons.delete),
              onPressed: () => _deleteSelected(),
            ),
          ],
          child: AppBar(title: const Text('Photo Gallery')),
        ),
        body: SelectionCanvas(
          child: GridView.builder(
            controller: _scrollController,
            itemCount: photos.length,
            itemBuilder: (context, index) => SelectableBuilder(
              key: ValueKey(photos[index].id), // Stable selection
              index: index,
              isSelectable: photos[index].canSelect,
              builder: (context, isSelected) => GestureDetector(
                onTap: () => _handleTap(index),
                child: PhotoTile(
                  photo: photos[index],
                  isSelected: isSelected,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Core Components #

SelectionMode #

The root widget that provides selection functionality:

SelectionMode(
  controller: _controller,           // Optional: provide your own controller
  scrollController: _scrollController, // For auto-scroll during drag
  options: SelectionOptions(...),    // Configure behavior
  onModeChanged: (enabled) => ...,   // Listen to mode changes
  onChanged: (selection) => ...,     // Listen to selection changes
  child: YourWidget(),
)

SelectableBuilder #

Makes individual items selectable:

SelectableBuilder(
  key: ValueKey(item.id),  // Optional: for stable selection
  index: index,            // Required: item index
  isSelectable: true,      // Optional: whether item can be selected
  builder: (context, isSelected) => ItemWidget(
    isSelected: isSelected,
  ),
)

Selection Behaviors #

Manual (Explicit Control) #

SelectionOptions(behavior: SelectionBehavior.manual)
// - Must call enable()/disable() explicitly
// - No automatic mode changes

Auto Enable (Default) #

SelectionOptions(behavior: SelectionBehavior.autoEnable)
// - Auto-enables on first item selection
// - Manual disable required

Auto Toggle (Implicit) #

SelectionOptions(behavior: SelectionBehavior.autoToggle)
// - Auto-enables on first item selection
// - Auto-disables when all items deselected

Selection Types #

Drag Selection #

Touch and drag between items to select ranges:

SelectionOptions(
  dragSelection: DragSelectionOptions(
    axis: Axis.vertical,          // Optional: constrain drag direction
    delay: Duration(milliseconds: 100), // Optional: prevent accidental drags
  ),
)

Working with Selected Items #

Direct Access (Static Data) #

// Direct index access - simple and fast
final selectedItems = controller.selection
    .map((index) => items[index])
    .toList();

Query API (Dynamic Data) #

// Query selected items fluently
final selectedPhotos = controller.selectedFrom(photos).toList();

// Transform selected items
final titles = controller
    .selectedFrom(contacts)
    .where((c) => c.isActive)
    .map((c) => c.name)
    .toList();

// Check selection state
if (controller.selectedFrom(items).hasAny) {
  print('${controller.selectedFrom(items).length} items selected');
}

Configuration Options #

SelectionOptions(
  behavior: SelectionBehavior.autoToggle,
  
  constraints: SelectionConstraints(
    maxSelections: 10,                    // Limit selections
  ),
  
  haptics: HapticFeedbackResolver.all,    // Haptic feedback
  
  autoScroll: SelectionAutoScrollOptions(
    edgeThreshold: 80,                    // Auto-scroll trigger distance
    scrollSpeed: 300,                     // Scroll speed (px/sec)
  ),
  
  dragSelection: DragSelectionOptions(
    axis: Axis.vertical,                  // Constrain drag direction
    delay: Duration(milliseconds: 100),   // Prevent accidental drags
  ),
)

Haptic Feedback #

// Predefined resolvers
HapticFeedbackResolver.all       // Feedback for all events
HapticFeedbackResolver.modeOnly  // Only mode enter/exit
HapticFeedbackResolver.none      // No haptic feedback

// Custom haptic resolver
void customHaptics(HapticEvent event) {
  switch (event) {
    case HapticEvent.itemSelected:
      HapticFeedback.lightImpact();
    case HapticEvent.maxItemsReached:
      HapticFeedback.heavyImpact();
    case HapticEvent.dragStart:
      HapticFeedback.mediumImpact();
    // Handle other events...
  }
}

Controller Methods #

Selection Control #

// Individual items
controller.toggleItem(index);
controller.selectAll(allIndices);
controller.deselectAll();
controller.invertSelection(allIndices);

// Range operations
controller.selectRange(0, 5);
controller.deselectRange(2, 4);
controller.toggleRange(1, 3);

// Mode control
controller.enable();
controller.disable();
controller.toggle();

UI Components #

Selection App Bar #

Automatically switches between normal and selection modes:

MaterialSelectionAppBar(
  actions: [
    IconButton(icon: Icon(Icons.share), onPressed: _share),
    IconButton(icon: Icon(Icons.delete), onPressed: _delete),
  ],
  selectionTitle: (context, count) => Text('$count selected'),
  onCancel: () => controller.disable(),
  child: AppBar(title: Text('My App')),
)

Action Bar #

Bottom action bar for selection operations:

SelectionActionBar(
  children: [
    IconButton(icon: Icon(Icons.share), onPressed: _share),
    IconButton(icon: Icon(Icons.delete), onPressed: _delete),
    IconButton(icon: Icon(Icons.copy), onPressed: _copy),
  ],
  borderRadius: BorderRadius.circular(20),
  animated: true,
)

Status Bar #

Display selection status:

SelectionStatusBar(
  leftActions: [IconButton(...)],
  rightActions: [IconButton(...)],
  statusBuilder: (context, count) => Text('$count selected'),
)

Selection Consumer #

Listen to selection changes:

SelectionConsumer(
  builder: (context, controller, child) {
    return controller.isActive 
      ? SelectionUI()
      : NormalUI();
  },
)

Drag Selection Ignore #

Ignore pointer events during drag selection:

DragSelectionIgnore(
  child: FloatingActionButton(...), // Won't interfere with drag selection
)

Keyboard Shortcuts #

Keyboard shortcuts are optional. Add them by wrapping with SelectionShortcuts:

SelectionMode(
  controller: _controller,
  child: SelectionShortcuts(
    totalItems: items.length,  // Required for "select all"
    child: Focus(
      autofocus: true,
      child: SelectionCanvas(
        child: YourWidget(),
      ),
    ),
  ),
)

Available Shortcuts #

  • Ctrl+A / Cmd+A - Select all items
  • Escape - Exit selection mode

Custom Layout Example #

// Wrap your layout in SelectionMode
SelectionMode(
  controller: _controller,
  child: Scaffold(
    body: Stack(
      children: [
        // Wrap your grid in SelectionCanvas for interaction
        SelectionCanvas(
          child: GridView.builder(
            itemBuilder: (context, index) => SelectableBuilder(
              index: index,
              builder: (context, isSelected) => GestureDetector(
                onTap: () => _controller.toggleItem(index),
                child: Container(
                  decoration: isSelected 
                    ? BoxDecoration(border: Border.all(color: Colors.blue))
                    : null,
                  child: Card(child: Center(child: Text('Item $index'))),
                ),
              ),
            ),
          ),
        ),

        // Custom status indicator
        SelectionConsumer(
          builder: (context, controller, _) => controller.isActive 
            ? Positioned(
                top: 20, left: 20,
                child: Chip(
                  label: Text('${controller.selection.length} selected'),
                  onDeleted: controller.disable,
                ),
              )
            : SizedBox.shrink(),
        ),

        // Custom action buttons
        SelectionConsumer(
          builder: (context, controller, _) => controller.isActive 
            ? Positioned(
                bottom: 20, right: 20,
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    FloatingActionButton(
                      onPressed: _share,
                      child: Icon(Icons.share),
                    ),
                    SizedBox(height: 8),
                    FloatingActionButton(
                      onPressed: _delete,
                      child: Icon(Icons.delete),
                    ),
                  ],
                ),
              )
            : SizedBox.shrink(),
        ),
      ],
    ),
  ),
)

Stable Selection with ValueKey #

Use ValueKey for data that changes during selection:

// With ValueKey - selection persists when list reordered or mutated
SelectableBuilder(
  key: ValueKey(item.id), // Selection stable across data changes
  index: index,
  builder: (context, isSelected) => ItemWidget(item),
)

// Without ValueKey - for static data
SelectableBuilder(
  index: index, // Works fine if data doesn't change
  builder: (context, isSelected) => ItemWidget(item),
)

Examples #

The package includes three complete example apps:

  • Basic List Demo - Simple list with manual selection mode
  • Grid Selection Demo - Photo grid with drag selection and auto-scroll
  • Mixed Selection Demo - Contact list with auto-toggle behavior and constraints

See /example folder for complete implementations showing different use cases and configurations.

3
likes
160
points
35
downloads

Publisher

verified publisherdegenk.com

Weekly Downloads

A Flutter package for multi-item selection with range selection

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, meta

More

Packages that depend on selection_mode