Search code examples
flutterflutter-layoutswipe-gesturegesturedetectorgesture-detection

How to recognize diagonal swipes in flutter?


I attempted to detect diagonal swipe direction using GestureDetector. I used the onPanStart method to detect the screen's corners. However, it is not perfect because it only considers the beginning of the gesture.

Right now, if I swipe bottom left to top right in the top right corner or part, it gives me the incorrect top right direction. In this case, the correct direction should be bottom left.

So, how can we identify diagonal swipe direction using GestureDetector or any other method?

I looked at several other StackOverflow posts, but they only detect top, left, right, and bottom directions.

Here is an example of code.

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    final screenSize = MediaQuery.of(context).size;
    String? swipeDirection;

    return Scaffold(
        body: SafeArea(
      child: Scaffold(
        backgroundColor: Colors.black,
        body: GestureDetector(
          child: Container(),
          onPanStart: (details) {
            final halfScreenWidth = screenSize.width / 2;
            final halfScreenHeight = screenSize.height / 2;
            final position = details.localPosition;

            if ((position.dx < halfScreenWidth) &&
                (position.dy < halfScreenHeight)) {
              swipeDirection = 'topLeft';
            } else if ((position.dx > halfScreenWidth) &&
                (position.dy < halfScreenHeight)) {
              swipeDirection = 'topRight';
            } else if ((position.dx < halfScreenWidth) &&
                (position.dy > halfScreenHeight)) {
              swipeDirection = 'bottomLeft';
            } else {
              swipeDirection = 'bottomRight';
            }
          },
        ),
      ),
    ));
  }
}

Solution

  • You can use pan update to find the position of the pointer the same manner you did for pan start and get the starting and ending points. Then on pan end execute with the final results

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: MyHome(),
        );
      }
    }
    
    class MyHome extends StatefulWidget {
      const MyHome({Key? key}) : super(key: key);
    
      @override
      State<MyHome> createState() => _MyHomeState();
    }
    
    class _MyHomeState extends State<MyHome> {
      String? swipeStart;
      String? swipeEnd;
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        final screenSize = MediaQuery.of(context).size;
        return SafeArea(
          child: Scaffold(
            backgroundColor: Colors.black,
            body: GestureDetector(
              child: Container(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height,
                color: Colors.grey,
              ),
              onPanStart: (details) {
                final halfScreenWidth = screenSize.width / 2;
                final halfScreenHeight = screenSize.height / 2;
                final position = details.localPosition;
    
                if ((position.dx < halfScreenWidth) &&
                    (position.dy < halfScreenHeight)) {
                  swipeStart = 'topLeft';
                } else if ((position.dx > halfScreenWidth) &&
                    (position.dy < halfScreenHeight)) {
                  swipeStart = 'topRight';
                } else if ((position.dx < halfScreenWidth) &&
                    (position.dy > halfScreenHeight)) {
                  swipeStart = 'bottomLeft';
                } else {
                  swipeStart = 'bottomRight';
                }
                // print(swipeStart);
              },
              onPanUpdate: (details) {
                final halfScreenWidth = screenSize.width / 2;
                final halfScreenHeight = screenSize.height / 2;
                final position = details.localPosition;
    
                if ((position.dx < halfScreenWidth) &&
                    (position.dy < halfScreenHeight)) {
                  swipeEnd = 'topLeft';
                } else if ((position.dx > halfScreenWidth) &&
                    (position.dy < halfScreenHeight)) {
                  swipeEnd = 'topRight';
                } else if ((position.dx < halfScreenWidth) &&
                    (position.dy > halfScreenHeight)) {
                  swipeEnd = 'bottomLeft';
                } else {
                  swipeEnd = 'bottomRight';
                }
                //print(swipeEnd);
              },
              onPanEnd: (details) {
                print("Final direction was from $swipeStart to $swipeEnd");
              },
            ),
          ),
        );
      }
    }
    

    I have printed output like this

    flutter: Final direction was from topLeft to bottomRight
    flutter: Final direction was from bottomLeft to topRight
    

    EDIT

    You can find the offset when the pan starts and updates. Then on pan end calculate the direction like this

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: MyHome(),
        );
      }
    }
    
    class MyHome extends StatefulWidget {
      const MyHome({Key? key}) : super(key: key);
    
      @override
      State<MyHome> createState() => _MyHomeState();
    }
    
    class _MyHomeState extends State<MyHome> {
      String? horizontalDirection;
      String? verticalDirection;
      late Offset startPoint;
      late Offset endPoint;
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return SafeArea(
          child: Scaffold(
            backgroundColor: Colors.black,
            body: GestureDetector(
              child: Container(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height,
                color: Colors.grey,
              ),
              onPanStart: (details) {
                startPoint = details.localPosition;
              },
              onPanUpdate: (details) {
                endPoint = details.localPosition;
              },
              onPanEnd: (details) {
                if (startPoint.dx < endPoint.dx) {
                  horizontalDirection = "right";
                } else {
                  horizontalDirection = "left";
                }
    
                if (startPoint.dy < endPoint.dy) {
                  verticalDirection = "bottom";
                } else {
                  verticalDirection = "top";
                }
                print("Final direction was $horizontalDirection$verticalDirection");
              },
            ),
          ),
        );
      }
    }