VOOZH about

URL: https://blog.logrocket.com/handling-gestures-flutter-gesturedetector/

⇱ Handling gestures in Flutter - LogRocket Blog


2021-10-18
1754
#flutter
Ivy Walobwa
71756
πŸ‘ Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

When it comes to creating applications, you have to handle user gestures such as touch and drags. This makes your application interactive.

πŸ‘ Handling Gestures in Flutter

To effectively handle gestures, you need to listen to the gestures and respond to them. Flutter offers a variety of widgets that help add interactivity to your apps.

In this article, we go through handling gestures with the GestureDetector widget.

πŸš€ Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

Introduction

Some widgets, like Container and Card widgets, don’t have an inbuilt way of detecting gestures. Such widgets are wrapped in the GestureDetector widget which is purely used for detecting gestures and does not give any visual response like a ripple effect.

The GestureDetector widget works by recognizing gestures that have callbacks defined and responding accordingly to the event. If a gesture is to be disabled, a null value is passed to the callback.

The following are common gestures captured by the GestureDetector widget, their corresponding events, and possible applications (all illustrations are credit to Luke Wroblewski’s Touch Gesture Reference Guide):

Tap

The user briefly touched the screen with a fingertip.

πŸ‘ Tap

  • onTapDown β€” triggered when user makes contact with screen, might be a tap
  • onTapUp β€” triggered when user stops making contact with the screen
  • onTap β€” triggered when user briefly touches the screen
  • onTapCancel β€” triggered when the event that fired onTapDown is not a tap

Possible applications for the tap gesture include:

  1. Select
  2. Cancel
  3. Submit

Double-tap

The user tapped the screen at the same location twice in quick succession.

πŸ‘ Double-tap

  • onDoubleTapDown β€” triggered when user makes contact with screen, might be a double tap
  • onDoubleTap β€” triggered when user taps the screen at the same location twice in quick succession
  • onDoubleTapCancel β€” triggered when the event that fired onDoubleTapDown is not a double tap

Possible applications for the double-tap gesture include:

  1. Like/dislike
  2. Screen on/off
  3. Resize an image

Long press

The user made contact with the screen at the same location for a long period of time.
πŸ‘ Long press

  • onLongPressDown β€” triggered when user makes contact with screen, might be a long press
  • onLongPressStart β€” triggered when the start of a long press has been detected
  • onLongPress β€” triggered when a long press has been detected
  • onLongPressMoveUpdate β€” triggered when long press has been detected and user has drag-moved finger
  • onLongPressEnd β€” triggered when the end of a long press has been detected
  • onLongPressUp β€” triggered when the end of a long press has been detected; contact has been removed after long press
  • onLongPressCancel β€” triggered when the event that fired onLongPressDown is not a long press

Possible applications for the long-press gesture include:

  1. Show more options
  2. Move an icon

Scale

The user pinched or spread the screen.

πŸ‘ Spread gesture Flutter
πŸ‘ Pinch gesture

  • onScaleStart β€” triggered when contact with the screen has established a focal point and initial scale of 1.0
  • onScaleUpdate β€” triggered when contact with the screen has indicated a new focal point and/or scale
  • onScaleEnd β€” triggered when user is no longer making contact with screenPossible application for the scale gesture

Uses for scale gestures include:

  1. Zoom in/zoom out
  2. Rotation

Vertical Drag

The user made contact with the screen and moved their fingertip in a steady manner vertically.

πŸ‘ Vertical drag

  • onVerticalDragDown β€” triggered when user makes contact with screen, might move vertically
  • onVerticalDragStart β€” triggered when user has made contact with screen and began to move vertically
  • onVerticalDragUpdate β€” triggered when contact that is moving vertically has moved in a vertical direction once again
  • onVerticalDragEnd β€” triggered when the end of a vertical drag has been detected
  • onVerticalDragCancel β€” triggered when the event that fired onVerticalDragDown is not a vertical drag

Possible applications for the vertical drag gesture include:

  1. Scroll

Horizontal drag

The user made contact with the screen and moved their fingertip in a steady manner horizontally.

πŸ‘ Horizontal drag

  • onHorizontalDragDown β€” triggered when user makes contact with screen, might move horizontally
  • onHorizontalDragStart β€” triggered when user has made contact with screen and began to move horizontally
  • onHorizontalDragUpdate β€” triggered when contact that is moving horizontally has moved in a horizontal direction once again
  • onHorizontalDragEnd β€” triggered when the end of a horizontal drag has been detected
  • onHorizontalDragCancel β€” triggered when the event that fired onHorizontalDragDown is not a horizontal drag

Possible applications for the horizontal drag gesture include:

  1. Delete
  2. Archive
  3. Navigate to a different view

This is not a complete list of the gestures detected. Check the official documentation for a complete list.


Over 200k developers use LogRocket to create better digital experiences

πŸ‘ Image
Learn more β†’

Let’s try it out!

Getting started

To use the GestureDetector widget:

  1. Wrap desired widget with the GestureDetector widget.
  2. Pass callback for the gesture you wish to detect.
  3. Update the app accordingly

We will build a simple demo app that handles the tap, double-tap, long press, and scale gestures.

Create a new Flutter app

Create a new Flutter application and clear the default code in your main.dart file.

Update UI

We will create the four files below. You can view the folder structure here.

main.dart

import 'package:flutter/material.dart';
import 'presentation/my_app_widget.dart';
void main() {
 runApp(const MyApp());
}

my_app_widget.dart

import 'package:flutter/material.dart';
import 'home_page.dart';
class MyApp extends StatelessWidget {
 const MyApp({Key? key}) : super(key: key);
 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Flutter Gesture Detector Demo',
 theme: ThemeData(
 primarySwatch: Colors.blue,
 ),
 home: const HomePage(),
 );
 }
}

home_page.dart

import 'package:flutter/material.dart';
import 'widgets/widgets.dart';
class HomePage extends StatelessWidget {
 const HomePage({Key? key}) : super(key: key);
 @override
 Widget build(BuildContext context) {
 final height = MediaQuery.of(context).size.height;
 final width = MediaQuery.of(context).size.width;
 return Scaffold(
 body: Padding(
 padding: EdgeInsets.symmetric(
 horizontal: width * 0.1, vertical: height * 0.2),
 child: Column(
 mainAxisAlignment: MainAxisAlignment.spaceBetween,
 children: const [
 MyCardWidget(),
 MyFavoriteIconWidget()
 ],
 ),
 ),
 );
 }
}

my_card_widget.dart

import 'dart:math';
import 'package:flutter/material.dart';
class MyCardWidget extends StatefulWidget {
 const MyCardWidget({
 Key? key,
 }) : super(key: key);
 @override
 State<MyCardWidget> createState() => _MyCardWidgetState();
}
class _MyCardWidgetState extends State<MyCardWidget> {
 @override
 Widget build(BuildContext context) {
 return const Card(
 child: SizedBox(
 height: 300,
 width: 300,
 ),
 color: Colors.yellow,
 );
 }
}

my_favorite_icon_widget.dart

import 'package:flutter/material.dart';
class MyFavoriteIconWidget extends StatefulWidget {
 const MyFavoriteIconWidget({
 Key? key,
 }) : super(key: key);

 @override
 State<MyFavoriteIconWidget> createState() => _MyFavoriteIconWidgetState();
}

class _MyFavoriteIconWidgetState extends State<MyFavoriteIconWidget> {
 @override
 Widget build(BuildContext context) {
 return const Icon(
 Icons.favorite_border,
 size: 40,
 );
 }
}

Your final app should look like this:

πŸ‘ Demo app Flutter gesture

Now that we have our UI ready, let’s handle some gestures.

Handling the tap gesture

In your my_favorite_icon_widget.dart file:

  1. Add a selected flag property to the StatefulWidget
    bool isSelected = false;
  2. Wrap the Icon widget with the GestureDetector widget
  3. Provide a non-null callback to the onTap property
  4. Change the icon and icon color based on the value of the flag property value
class _MyFavoriteIconWidgetState extends State<MyFavoriteIconWidget> {
 bool isSelected = false;

 @override
 Widget build(BuildContext context) {
 return GestureDetector(
 onTap: (){
 setState(() {
 isSelected = !isSelected;
 });
 },
 child: Icon(
 isSelected ? Icons.favorite: Icons.favorite_border,
 size: 40,
 color: isSelected? Colors.red: Colors.black ,
 ));
 }
}

Handling the double-tap gesture

In your my_card_widget.dart file:

  1. add a color property
  2. wrap the Card widget with the GestureDetector widget
  3. provide a non-null callback to the onDoubleTap property
  4. change the color of the card based on the value of the color property
class _MyCardWidgetState extends State<MyCardWidget> {
 Color bgColor = Colors.yellow;
 @override
 Widget build(BuildContext context) {
 return GestureDetector(
 onDoubleTap: (){
 setState(() {
 bgColor = Colors.primaries[Random().nextInt(Colors.primaries.length)];
 });
 },
 child: Card(
 child: const SizedBox(
 height: 300,
 width: 300,
 ),
 color: bgColor,
 ),
 );
 }
}

Handling the long press gesture

In your my_card_widget.dart file:
1. Add a makeCircular flag property
2. Provide a non-null callback to the onLongPress property
3. Change the shape of the card based on the value of the makeCircular property

class _MyCardWidgetState extends State<MyCardWidget> {
 Color bgColor = Colors.yellow;
 bool makeCircular = false;
 @override
 Widget build(BuildContext context) {
 return GestureDetector(
 onLongPress: (){
 setState(() {
 makeCircular = !makeCircular;
 });
 },
 child: Card(
 shape: makeCircular? const CircleBorder(): const RoundedRectangleBorder(),
 child: const SizedBox(
 height: 300,
 width: 300,
 ),
 color: bgColor,
 ),
 );
 }
}

Handling the scale gesture

In your my_card_widget.dart file:
1. Add a _scaleFactor property
2. Add a _baseFactor property
3. Provide a non-null callback to the onScaleStart property β€” establish an initial scale
4. Provide a non-null callback to the onScaleUpdate property β€” establish a new scale
5. Provide a non-null callback to the onScaleEnd property β€” return to initial scale
6. Wrap the Card widget with Transorm.scale widget
7. Change the scale property based on the value of the _scaleFactor

class _MyCardWidgetState extends State<MyCardWidget> {
 Color bgColor = Colors.yellow;
 bool makeCircular = false;
 double _scaleFactor = 0.5;
 double _baseScaleFactor = 0.5;
 @override
 Widget build(BuildContext context) {
 return GestureDetector(
 onScaleStart: (details){
 _baseScaleFactor = _scaleFactor;
 },
 onScaleUpdate: (details){
 setState(() {
 _scaleFactor = _baseScaleFactor * details.scale;
 });
 },
 onScaleEnd: (details){
 // return to initial scale
 _scaleFactor = _baseScaleFactor;
 },
 child: Transform.scale(
 scale: _scaleFactor,
 child: Card(
 shape: makeCircular? const CircleBorder(): const RoundedRectangleBorde(),
 child: const SizedBox(
 height: 300,
 width: 300,
 ),
 color: bgColor,
 ),
 );
 }
}

The video below shows the implemented gestures:

Gesture Disambiguation

So what happens when we provide the onGestureDown event callback for tap and double-tap, and two delayed, brief touch events occur?

Consider the illustration:

πŸ‘ Gesture Disambiguation Flutter

When two or more gesture events with non-null callbacks are recognized, Flutter disambiguates which gesture the user intends by having each recognizer join the gesture arena. In the gesture arena, the events β€œbattle” and the winning event takes effect while the losing events are canceled. 

The gesture arena takes into account the following factors:

  1. The length of time the user touches the screen
  2. The number of pixels moved in each direction
  3. Which gesture is in the arena
  4. Which gesture declares victory

These are the battle states:

  • Maybe β€” might be the gesture
  • Hold β€” might be the gesture if it evolves in a particular way; for our case, one tap occurred and might be a double tap if the second tap occurs within expected time
  • Yes β€” declaration of victory
  • Cancel β€” withdrawn from battle

For example, say the following occur:

1. onTapDown and onDoubleTapDown are triggered
2. The two gestures compete
3. The tap gesture wins and the callback is executed (the onTap callback)
4. The double-tap gesture loses and gets canceled (onDoubleTapCancel triggered)

For our case, the tap gesture won because:

  • The duration between the two taps was delayed
  • The tap gesture declared victory with a β€œyes”
  • The tap gesture is the remaining gesture after double-tap got canceled, with no other competitor

Conclusion

We have gone through the GestureDetector widget and learned how it works. We have learned how to use it to add interactivity to our application, and we have implemented some of the common gestures, like tap, long press, double-tap, and scale. We finally looked at gesture disambiguation.


More great articles from LogRocket:


With this knowledge, we now have a better understanding of the GestureDetector widget and can comfortably use any of its properties to recognize gestures. Feel free to play around with the different gestures. You can find the demo app on GitHub.

Get set up with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not server-side

    $ npm i --save logrocket 
    
    // Code:
    
    import LogRocket from 'logrocket'; 
    LogRocket.init('app/id');
     
    // Add to your HTML:
    
    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
     
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
πŸ‘ Image
πŸ‘ Image
πŸ‘ Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

Debug Next.js apps with AI agents and next-browser

Learn how next-browser gives AI agents runtime context for debugging Next.js apps, including React props, hydration, PPR, forms, and performance.

πŸ‘ Image
Emmanuel John
Jun 17, 2026 β‹… 9 min read

Stop hardcoding LLM SDKs: Dynamic LLM routing with OpenRouter and Next.js

Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.

πŸ‘ Image
Chizaram Ken
Jun 16, 2026 β‹… 13 min read

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension β€” no new framework required.

πŸ‘ Image
Ikeh Akinyemi
Jun 12, 2026 β‹… 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo β€” with email/password login, Google OAuth, session persistence, and protected routes.

πŸ‘ Image
Chinwike Maduabuchi
Jun 9, 2026 β‹… 13 min read
View all posts

Would you be interested in joining LogRocket's developer community?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now