Last active
July 13, 2025 14:09
Flutter Sample code to detect 2 finger swipe vertically using `RawGestureDetector` and `MultiDragGestureRecognizer`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/gestures.dart'; | |
import 'package:flutter/material.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Swipe Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: SwipeDemo(), | |
); | |
} | |
} | |
class SwipeDemo extends StatefulWidget { | |
@override | |
SwipeDemoState createState() => SwipeDemoState(); | |
} | |
class SwipeDemoState extends State<SwipeDemo> { | |
Offset offset = Offset.zero; | |
@override | |
Widget build(BuildContext context) { | |
return SafeArea( | |
child: TwoFingerPointerWidget( | |
onUpdate: (details) { | |
setState(() { | |
offset += details.delta; | |
}); | |
}, | |
child: Container( | |
alignment: Alignment.center, | |
color: Colors.white, | |
child: Transform.translate( | |
offset: offset, | |
child: Container( | |
width: 100, | |
height: 100, | |
color: Colors.red, | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
class TwoFingerPointerWidget extends StatelessWidget { | |
final Widget child; | |
final OnUpdate onUpdate; | |
TwoFingerPointerWidget({this.child, this.onUpdate}); | |
@override | |
Widget build(BuildContext context) { | |
return RawGestureDetector( | |
gestures: <Type, GestureRecognizerFactory>{ | |
CustomVerticalMultiDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomVerticalMultiDragGestureRecognizer>( | |
() => CustomVerticalMultiDragGestureRecognizer(), | |
(CustomVerticalMultiDragGestureRecognizer instance) { | |
instance.onStart = (Offset position) { | |
return CustomDrag(events: instance.events, onUpdate: onUpdate); | |
}; | |
}, | |
), | |
}, | |
child: child, | |
); | |
} | |
} | |
typedef OnUpdate(DragUpdateDetails details); | |
class CustomDrag extends Drag { | |
final List<PointerDownEvent> events; | |
final OnUpdate onUpdate; | |
CustomDrag({this.events, this.onUpdate}); | |
@override | |
void update(DragUpdateDetails details) { | |
super.update(details); | |
final delta = details.delta; | |
if (delta.dy.abs() > 0 && events.length == 2) { | |
onUpdate?.call(DragUpdateDetails( | |
sourceTimeStamp: details.sourceTimeStamp, | |
delta: Offset(0, delta.dy), | |
primaryDelta: details.primaryDelta, | |
globalPosition: details.globalPosition, | |
localPosition: details.localPosition, | |
)); | |
} | |
} | |
@override | |
void end(DragEndDetails details) { | |
super.end(details); | |
} | |
} | |
class CustomVerticalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_CustomVerticalPointerState> { | |
final List<PointerDownEvent> events = []; | |
@override | |
createNewPointerState(PointerDownEvent event) { | |
events.add(event); | |
return _CustomVerticalPointerState(event.position, onDisposeState: () { | |
events.remove(event); | |
}); | |
} | |
@override | |
String get debugDescription => 'custom vertical multidrag'; | |
} | |
typedef OnDisposeState(); | |
class _CustomVerticalPointerState extends MultiDragPointerState { | |
final OnDisposeState onDisposeState; | |
_CustomVerticalPointerState(Offset initialPosition, {this.onDisposeState}) : super(initialPosition); | |
@override | |
void checkForResolutionAfterMove() { | |
if (pendingDelta.dy.abs() > kTouchSlop) { | |
resolve(GestureDisposition.accepted); | |
} | |
} | |
@override | |
void accepted(GestureMultiDragStartCallback starter) { | |
starter(initialPosition); | |
} | |
@override | |
void dispose() { | |
onDisposeState?.call(); | |
super.dispose(); | |
} | |
} |
Updated version for the Flutter 3.0.5
https://gist.github.com/awaik/7d5b6d6ec644ae844a3740dddc990ed4
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@Robert12-git That would be really nice if you could share an up-to-date working example! :)