Introduction
Drag-to-select and multi-select lists are common in productivity and file-management apps. On mobile, the UX should be natural: tap to toggle, long-press to start multi-select mode, and drag to expand or shrink the selection while keeping smooth scrolling. This tutorial shows a practical, code-forward approach in Flutter for implementing drag selection and multi-select lists, covering state management, hit testing for drag gestures, visual feedback, and performance considerations for large lists.
Implementation Overview
The model is simple: maintain a Set of selected indices and an optional active selection range during a drag. Use a Scrollable list widget (ListView.builder) for content and wrap each item in a GestureDetector or Listener for pointer events. Long-press initiates selection mode; from there, pointer move events compute which item index the finger is over and update the selection Set accordingly. Keep UI updates efficient by only rebuilding affected items with optimized item widgets.
Key parts:
Selection state: Set selected
Drag state: int? dragStartIndex, int? dragCurrentIndex
Index hit testing: convert global/local pointer position to item index
Building Selectable List
Structure the list so each child can reflect selection state and expose its RenderBox for hit testing. A common pattern is to store GlobalKeys for visible child containers or compute item height when items are uniform.
If items are fixed height, index-from-position is trivial: index = (scrollOffset + localY) ~/ itemHeight. If variable height, wrap each item with a GlobalKey and check RenderBox.size and localToGlobal offsets while iterating visible indices.
Minimal item builder with fixed height:
final itemHeight = 64.0;
int indexFromLocalPosition(Offset local, double scrollOffset){
final y = scrollOffset + local.dy;
return (y ~/ itemHeight).clamp(0, itemCount - 1);
}Each item visually responds using a Container decoration or ListTile selected color. Prefer StatelessWidgets that take a selected boolean; this keeps rebuilds cheap when using itemCount large and ListView.builder.
Drag Selection Logic
Core flow:
Long-press on an item: set dragStartIndex and add it to selected.
On pointer move: compute new hover index, set dragCurrentIndex, and derive selected range between dragStartIndex and dragCurrentIndex.
On pointer up/cancel: finalize selection and clear drag state.
Listen to pointer events with a Listener at the list level so a single stream controls selection even while scrolling. Example pseudo-listener callbacks:
onPointerDown: check long-press timer or start selection directly when in multi-select mode
onPointerMove: if dragging, compute index and update Set
onPointerUp/onPointerCancel: stop dragging
Keep updates idempotent: compute newRange = [min..max] and then set selected = baseSelected ∪ newRange. Avoid rebuilding the entire list by calling setState only when the Set actually changes.
Small selection update example for drag range computation:
void updateDragSelection(int start, int current){
final a = start < current ? start : current;
final b = start < current ? current : start;
final newRange = Set<int>.from(List.generate(b - a + 1, (i) => a + i));
selected..removeWhere((i) => i >= 0 && i < itemCount && !newRange.contains(i))
..addAll(newRange);
setState((){});
}Performance And Accessibility
Performance tips:
Use ListView.builder with itemExtent when items are fixed height; this enables virtualization and index math.
Minimize rebuilds: pass only selected boolean into item widgets and make them const where possible.
For very large datasets, batch selection updates with a debounce (e.g., update state at 30–60Hz) to avoid jank while dragging.
Accessibility and UX:
Provide spoken feedback for screen readers when entering multi-select mode and when selection count changes.
Respect platform conventions: provide clear visual affordances (selection checkboxes, highlight) and an easy way to exit multi-select (tap a cancel button or back navigation).
Ensure hit areas are large enough and that drag gestures don’t conflict with horizontal swipe-to-dismiss unless explicitly required.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
Implementing drag select and multi-select in Flutter is a matter of combining reliable index hit-testing, concise selection state management, and efficient widget updates. Use fixed-height math when possible, a single pointer event stream for drag logic, and keep UI feedback immediate but debounced for heavy updates. With careful attention to performance and accessibility, you can build a natural, mobile-friendly selection experience that scales to large lists and complex UI requirements.