Irbisa · Cheatsheet
Gestures & Pointer Input
Gesture arena, hit testing, drag and scale, custom recognizers, keyboard, hover, drag-and-drop
What is the difference between
GestureDetector,InkWellandListener, and when do you use each?What is the difference between
IgnorePointerandAbsorbPointer?// A decorative layer on top: taps fall through to the list below.Stack( children: [ const ProductList(), IgnorePointer(child: ConfettiLayer(controller: _confetti)), ],…How do you implement swipe-to-delete with
Dismissible, and why must itskeycome from the item rather than the index?ListView.builder( itemCount: tasks.length, itemBuilder: (context, i) { final task = tasks[i]; return Dismissible( key: ValueKey(task.id), // identity, not position…How does Flutter's gesture arena decide which gesture wins, and why does
onTapfire only when the finger lifts?What do
HitTestBehavior.deferToChild,opaqueandtranslucentdo, and why do taps on empty space inside aGestureDetectorget lost?// Dead zone: the gap between the icon and the text paints nothing.GestureDetector( onTap: _open, // behavior: deferToChild by default child: const Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [Icon(Icons.folder), Text('Reports')],…In this code, what is printed when the user taps the star once, and what when they tap the padding around it?
GestureDetector( onTap: () => print('card'), onDoubleTap: () => print('card: double'), child: Padding( padding: const EdgeInsets.all(24), child: GestureDetector( onTap: () => print('star'), child: const Icon(Icons.star), ), ),)An overlay with
HitTestBehavior.translucentsits over a card so that both react to a tap. What does this print when the card is tapped, and why?Stack( children: [ GestureDetector( onTap: () => print('card'), child: const ColoredBox(color: Colors.white, child: SizedBox.expand()), ), Positioned.fill( child: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => print('overlay'), ), ), ],)Find the bug: leak_tracker reports an undisposed
TapGestureRecognizerfor this terms-of-service link, and the count grows on every rebuild.class TermsNotice extends StatelessWidget { const TermsNotice({super.key, required this.onOpenTerms}); final VoidCallback onOpenTerms; @override Widget build(BuildContext context) => Text.rich( TextSpan(text: 'By continuing you accept the ', children: [ TextSpan( text: 'Terms of Service', style: const TextStyle(decoration: TextDecoration.underline), recognizer: TapGestureRecognizer()..onTap = onOpenTerms, ), ]), );}You add
onDoubleTapto a list row that already hasonTap, and the whole screen starts to feel laggy: what did that one line cost?Swipe-to-archive rows sit inside a vertical
ListView, and a diagonal swipe sometimes scrolls the list and sometimes moves the row: what decides it?// Pan needs 36 px in any direction before it claims, so the row starts late and you also get dy.GestureDetector( onPanUpdate: (d) => setState(() => _dx += d.delta.dx), child: const MessageRow(),);…This pinch-to-zoom code throws in debug, and once that is fixed the zoom runs away after a few frames. What is wrong with it?
GestureDetector( onPanUpdate: (d) => setState(() => _offset += d.delta), onScaleUpdate: (d) => setState(() => _scale *= d.scale), child: Transform.translate( offset: _offset, child: Transform.scale(scale: _scale, child: const MapSurface()), ),)How do you build drag-and-drop with
DraggableandDragTarget, and why does the dragged copy often render without styles?LongPressDraggable<Task>( // waits, so the ListView keeps its scroll data: task, feedback: Material( // the Overlay has no Material above it elevation: 8, borderRadius: BorderRadius.circular(12), child: SizedBox(width: 260, child: TaskCard(task: task)),…Find the bug: the sticker jumps down and sideways the moment the user starts dragging it, then follows the finger at an offset.
class _StickerBoardState extends State<StickerBoard> { Offset _pos = const Offset(40, 40); @override Widget build(BuildContext context) => Stack( children: [ Positioned( left: _pos.dx, top: _pos.dy, child: GestureDetector( onPanUpdate: (d) => setState(() => _pos = d.globalPosition), child: const StickerCard(), ), ), ], );}Your swipe-to-delete commits on a flick but not when the user drags far, pauses and then lifts: what is
DragEndDetails.velocityreporting?// Inside the State of a swipe row; _c is an AnimationController (0..1 = row travel).static const double _width = 320;static const _spring = SpringDescription(mass: 1, stiffness: 500, damping: 30); void _onUpdate(DragUpdateDetails d) => _c.value -= d.primaryDelta! / _width;…How do you add double-tap-to-zoom to an
InteractiveViewer, and why does the image jump when the user pinches during the zoom animation?class _ZoomableState extends State<Zoomable> with SingleTickerProviderStateMixin { final _transform = TransformationController(); late final _anim = AnimationController( vsync: this, duration: const Duration(milliseconds: 250), )..addListener(() => _transform.value = _zoom!.value);…How do you add keyboard shortcuts with
Shortcuts,ActionsandFocus, and why does Ctrl+S sometimes do nothing at all?class SaveIntent extends Intent { const SaveIntent();} Widget buildEditor() { return Shortcuts(…A button in the corner of a card is dead: no ripple, no callback, nothing in the logs. How do you find out where that tap actually goes?
import 'package:flutter/gestures.dart';import 'package:flutter/rendering.dart'; void main() { debugPaintPointersEnabled = true; // highlights every box the hit test reaches debugPrintHitTestResults = true; // prints the hit path, deepest first…What does
RawGestureDetectorgive you overGestureDetector, and how do you write a custom recognizer such as a two-finger-only drag?class TwoFingerDragRecognizer extends OneSequenceGestureRecognizer { ValueChanged<Offset>? onDrag; final Set<int> _pointers = {}; @override void addAllowedPointer(PointerDownEvent event) {…How do mouse wheel, trackpad and stylus input reach a Flutter canvas, and what does a canvas built only on touch gestures miss on desktop?
Listener( onPointerSignal: (event) { if (event is! PointerScrollEvent) return; // Claim the tick, or a scroll view behind the canvas scrolls on it too. GestureBinding.instance.pointerSignalResolver.register(event, (e) { final dy = (e as PointerScrollEvent).scrollDelta.dy;…With VoiceOver or TalkBack on, your drag-to-set brightness control cannot be moved at all. What happens to pointer input when a screen reader is running?
// Pointer-only: with a screen reader on, the user's swipes never arrive here.Listener( onPointerMove: (e) => _setBrightness(e.localPosition.dx / _width), child: const BrightnessBar(),);…