Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter: Using both Pan and Scale gestures together?

I am creating a drawing canvas with flutter for which I want to use both scale (for zooming, panning and rotating) and Pan (for drawing with fingers) gestures but flutter doesn't allow that since Scale is a superset of Pan. Is there any workaround for this?

I tried to create my own custom gesture recognizer for detecting the number of pointers on the screen so that if two pointers come in contact with the screen within a short interval of time(let's say 1 second) Scale takes over otherwise pan starts working with the 1st pointer that touched the screen. I am using matrix_gesture_detector package for doing the scaling part.

Here is my code for the custom gesture recognizer

class ScaleAndPanRecognizer extends OneSequenceGestureRecognizer {
  var manager = GestureArenaManager();
  final Function onPanDown;
  final Function onPanUpdate;
  final Function onPanEnd;
  ScaleAndPanRecognizer({
    @required this.onPanDown,
    @required this.onPanUpdate,
    @required this.onPanEnd,
  });

  int numPointers = 0;
  int timeFrame = 1;
  int pointer1;
  int pointer2;
  var time1;
  var time2;
  var position1;

  @override
  void addPointer(PointerDownEvent event) {
    print(numPointers);
    if (numPointers == 0) {
      pointer1 = event.pointer;
      print(pointer1);
      position1 = event.localPosition;
      manager.hold(pointer1); //hold the assignment for this pointer for now
      startTrackingPointer(pointer1);

      numPointers += 1;
      time1 = DateTime.now();
    } else if (numPointers == 1) {
      pointer2 = event.pointer;
      print(pointer2);
      resolvePointer(pointer2, GestureDisposition.rejected);
      time2 = DateTime.now();
      var diff = time2.difference(time1);
      print(diff);
      if (diff <= Duration(seconds: timeFrame)) {
        manager.release(pointer1);
        resolvePointer(pointer1, GestureDisposition.rejected);
      } else {
        resolvePointer(pointer1, GestureDisposition.accepted);
        manager.release(pointer1);
        onPanDown(position1);
      }
      numPointers = 0;
    }
  }

  @override
  void handleEvent(PointerEvent event) {
    if (event is PointerMoveEvent) {
      onPanUpdate(event.localPosition);
    }
    if (event is PointerUpEvent) {
      onPanEnd();
      stopTrackingPointer(event.pointer);
    }
  }

  @override
  String get debugDescription => 'only one pointer recognizer';

  @override
  void didStopTrackingLastPointer(int pointer) {}

}
like image 487
Stack101 Avatar asked Apr 28 '20 20:04

Stack101


2 Answers

What you want to do is to replace onPanUpdate, onPanEnd... with onScaleUpdate, onScaleEnd.... Then just instead of details.globalPosition use details.focalPoint. You will get the same behaviour as Pan, plus you will now have access to details.scale or details.rotation. Here is an example.

Before:

onPanUpdate: (DragUpdateDetails details){
              newPosition = details.globalPosition;
              //scale and rotation missing in pan
            },

After:

onScaleUpdate: (ScaleUpdateDetails details){
              newPosition = details.focalPoint;
              newRotation = details.rotation;
              newScale = details.scale;
              //scale and rotation now available
            },

Hope this helps. Took me some time to figure it out too.

like image 150
MartinM Avatar answered Oct 20 '22 11:10

MartinM


Using Listener widget for listen pointer event, then handle pointer event to detect scale and pan gesture as you need.

  • 1 pointer (down and up in short interval time) => Tap
  • 1 pointer (down and up in long interval time) => Double Tap
  • 1 pointer (down and moving) => Pan
  • 2 pointer => scale and rotate

you can using gesture_x_detector for full support gestures(tap, double tap, scale, rotate, long press,..) together.

like image 45
Táo Đỏ Avatar answered Oct 20 '22 11:10

Táo Đỏ