Disable Multi Touch On A Widget In Flutter

In this post we will build a widget that can disable multi touch on a widget in Flutter app.

Introduction

Sometimes, you might need to disable multi touch or tap on a widget in your Flutter application. For example there is a list of items and only one of those items should be clickable at once. You do not want users to tap or touch with three fingers simultaneously and select three items at once.

Basically, you want to prevent users from multi-tapping or multi-touching.

DecisionMentor app
Multi touch on list
Multi touch on list

So how can we prevent multi tap or multi touch gestures?

Setup

Let’s say we have a simple list of items that creates a list widget as shown in the .gif above:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

class MyList extends StatelessWidget {
  List<String> myList = [
    "January",
    "February",
    "March",
    "April",
    "June",
    "July",
    "August"
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
            child: ListView.builder(
                shrinkWrap: true,
                itemCount: myList.length,
                physics: ClampingScrollPhysics(),
                itemBuilder: (BuildContext cxtListBuilder, int itemIdx) {
                  return ListTile(
                    title: Text(myList[itemIdx]),
                    onTap: () => print('tapped'),
                  );
                })));
  }
}

Currently, each item on the list like name of months supports multi tap gesture. We want to prevent this action.

Creating A Custom Gesture Recognizer

Flutter allows creating custom gesture recognizer widgets with the help of GestureRecognizer base class. There are already two abstract implementations of this class for multi tap and single tap gestures.

Since we are looking to implement a single tap gesture, we will create an implementation class for OneSequenceGestureRecognizer.

Implementation For Single Touch

Start by creating a custom widget that can allow it’s child widgets to have single touch gesture only.

class SingleTouchRecognizerWidget extends StatelessWidget {
  final Widget child;
  SingleTouchRecognizerWidget({this.child});

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return null;
  }
}

In it’s build function, we will return a gesture detector widget that supports single touch gesture only. So, let’s create another class called _SingleTouchRecognizer for this purpose

class _SingleTouchRecognizer extends OneSequenceGestureRecognizer {
  @override
  // TODO: implement debugDescription
  String get debugDescription => null;

  @override
  void didStopTrackingLastPointer(int pointer) {
    // TODO: implement didStopTrackingLastPointer
  }

  @override
  void handleEvent(PointerEvent event) {
    // TODO: implement handleEvent
  }
}

We will come back to the implementation of
_SingleTouchRecognizer later. For now, let’s complete setup of SingleTouchRecognizerWidget widget.

class SingleTouchRecognizerWidget extends StatelessWidget {
  final Widget child;
  SingleTouchRecognizerWidget({this.child});

  @override
  Widget build(BuildContext context) {
    return RawGestureDetector(
      gestures: <Type, GestureRecognizerFactory>{
        _SingleTouchRecognizer: GestureRecognizerFactoryWithHandlers<_SingleTouchRecognizer>(
          () => _SingleTouchRecognizer(),
          (_SingleTouchRecognizer instance) {},
        ),
      },
      child: child,
    );
  }
}

The build method now returns a RawGestureDetector that handles gestures from _SingleTouchRecognizer class.

Next, we need to implement the methods in our recognizer class.

We start by overriding addAllowedPointer method of GestureRecognizer.

class _SingleTouchRecognizer extends OneSequenceGestureRecognizer {

  int _p = 0;
  @override
  void addAllowedPointer(PointerDownEvent event) {
    //first register the current pointer so that related events will be handled by this recognizer
    startTrackingPointer(event.pointer);
    //ignore event if another event is already in progress
    if (_p == 0) {
      resolve(GestureDisposition.rejected);
      _p = event.pointer;
    } else {
      resolve(GestureDisposition.accepted);
    }
  }
...

Here, the startTrackingPointer method registers the related events to be handled by the recognizer. Then resolve function is responsible for ensuring if the touch event should be allowed to be continued or not.

Parameters For Resolve

If we pass GestureDisposition.rejected, the current touch event is not resolved. So this touch event will be passed down and allowed to continue. However if GestureDisposition.accepted is passed, then touch event is resolved and no other events are called past this.

We want to resolve a tap event if there is already another touch event created preventing multi-tap/multi-touch.

Finally, we reset the value of control variable _p via the handleEvent function.

...
@override
  void handleEvent(PointerEvent event) {
    if (!event.down && event.pointer == _p) {
      _p = 0;
    }
  }
...

This completes our implementation of _SingleTouchRecognizer class.

Now, just wrap the widget which needs multi-tap, multi-touch disabled by the custom widget you just created.

...
child: SingleTouchRecognizerWidget(
                child: ListView.builder(

...

Reference: StackOverflow

Check out more useful articles on Flutter here.