16

I have problems to get real local position on gesture detector widget using pan update.

new Center(    
  child new Container(
       height: 150.0,
       width: 150.0,
       decoration: new BoxDecoration(
         borderRadius: new BorderRadius.circular(5.0),
         color: Colors.white,
      ),
      child: new GestureDetector(
      onPanUpdate: (details) => onPanUpdate(context, details),
      child: new CustomPaint(
         painter: new MyPainter(
             points: points,
             limitBottom:height - 125.0,
             limitTop: 10.0,
             limitLeft: 5.0,
             limitRight: width - 55.0
         ),
       ),
    ),
  ),  
)

when I print the the Offset of global position && local position, I've got same value for both of them. in Result it was painted outside of my Container widget. Is there anything I missed to get local position?

Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
M. Plant
  • 467
  • 2
  • 7
  • 19
  • It would be helpful if you included the code where you actually do the check for local & global position. However I can probably still tell you what's going on. – rmtmckenzie Aug 24 '18 at 19:51

4 Answers4

23

I assume you're using something like this to get the local position:

RenderBox getBox = context.findRenderObject();
var local = getBox.globalToLocal(start.globalPosition);

The reason you'd be getting the wrong offset after doing that has to do with the context you're using to find the local position.

If you're using the overall widget's context, essentially what you're doing is calculating the offset within the overall widget. i.e.

YourWidget <-- context
  Center
   Container
     GestureDetector
     ...

Let's say that the screen looks something like this:

1__________________________   
|                          |  <--- YourWidget
|       2_________         |
|       | gesture |        |
|       | detector|        |
|       |   3.    |        |
|       |_________|        |
|                          |
|__________________________|

With 1 being the top left of your overall widget (and the screen), 2 being the top left of your gesture detector, and 3 being the point where you tap.

By using the context of YourWidget, you're calculating the position of the tap based on the difference between 1 and 3. If 1 happens to be at the top left of the screen, the result will match the global coordinates.

By using the context for your gesture detector, you'd instead be measuring the distance between 2 and 3, which will give you the offset you want.

There's two ways to fix this - either you can wrap your GestureDetector in a Builder widget, or you can create a new Stateful/Stateless widget that encapsulates just the GestureDetector. I'd personally recommend creating a new widget.

rmtmckenzie
  • 37,718
  • 9
  • 112
  • 99
  • when i print the the Offset of global position && local position, i've got same value for both of them. i wrote those value same because in event pan update i do transform global to local thing. it's still got same value. – M. Plant Aug 27 '18 at 02:34
  • Did you wrap your gesture detector in a Builder or create a new Stateless widget for it? Because otherwise it's the expected behavior as per my explanation. – rmtmckenzie Aug 27 '18 at 04:46
  • And if you did change something but are still having the same issue, please update your question with the new code – rmtmckenzie Aug 27 '18 at 04:46
  • Thanks you very much appreciate it ! – BOT Axel Jul 08 '19 at 12:41
3

I was having a problem with my "tap position" being offset. I believe it was the same issue mentioned above. I was able to solve this similar problem by reading about keys and finding the location/size of widgets here:

https://medium.com/@diegoveloper/flutter-widget-size-and-position-b0a9ffed9407

So I didn't have to create another widget or layout builder as mentioned above, but I was able to just add a global key and then grab the position of the tap by doing the following with "currentContext" that comes from the key. Very cool!

  void _handleLongPress(LongPressStartDetails details) {
    final RenderBox referenceBox = _keyRink.currentContext.findRenderObject();
    var x = referenceBox.globalToLocal(details.globalPosition);
    if (x == tapPosition) {
      Vibration.vibrate(duration: 50);
      setState(() {
        _shots[_homeShotCount] = tapPosition;
      });
    }
  }

Here was my main scaffold where you can see the spot I added the key.

   return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: GestureDetector(
        key: _keyRink,
        onTapDown: _getTapPosition,
        onLongPressStart: _handleLongPress,
        child: Stack(
          children: <Widget>[
            AnimatedOpacity(
              duration: Duration(milliseconds: 2500),
              opacity: _rinkOpacity,
              child: Center(
                child: Image.asset('assets/rink.png',
                    width: size.width, height: size.height, fit: BoxFit.fill),
              ),
            ),
            Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Transform.rotate(
                    angle: animationRotate.value,
                    child: Transform.scale(
                        scale: animationDrop.value,
                        child: FlatButton(
                            onPressed: null,
                            padding: EdgeInsets.all(0.0),
                            child: Image.asset('assets/puck.png'))),
                  )
                ],
              ),
            ),
            CustomPaint(
              painter: ShapesPainter(),
              child: FractionallySizedBox(heightFactor: 1.0,widthFactor: 1.0,),
            ),
          ],
        ),
      ),
      drawer: Drawer(
        child: drawerItems,
      ),
    );
  }
Eradicatore
  • 1,501
  • 2
  • 20
  • 38
2

Wrapping it with a StatelessWidget works fine for me, thanks to @rmtmckenzie

Here my code:

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(),
      body: _Foo(),
    );
  }
}

class _Foo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onLongPressDragStart: (details) => _onLongPressDragStart(details, context),
      onLongPressDragUpdate: (details) => _onLongPressDragUpdate(details, context),
      onLongPressDragUp: (details) => _onLongPressDragUp(details, context),
      child: FlareActor(
        "assets/test.flr",
        alignment: Alignment.center,
        fit: BoxFit.contain,
      ),
    );
  }

  void _onLongPressDragStart(GestureLongPressDragStartDetails details, BuildContext context) {
    print('_onLongPressDragStart details: ${details.globalPosition}');
  }

  void _onLongPressDragUpdate(GestureLongPressDragUpdateDetails details, BuildContext context) {
    var localTouchPosition = (context.findRenderObject() as RenderBox).globalToLocal(details.globalPosition);
    print('_onLongPressDragUpdate details: ${details.globalPosition} - localTouchPosition: $localTouchPosition');
  }

  void _onLongPressDragUp(GestureLongPressDragUpDetails details, BuildContext context) {
    print('_onLongPressDragUp details: ${details.globalPosition}');
  }
}

The important things happen in the _Foo class.

Ralph Bergmann
  • 3,015
  • 4
  • 30
  • 61
2

You can get the local position of a GestureDetector using the DragUpdateDetails.localPosition

GestureDetector(
  onPanUpdate: (DragUpdateDetails details) {
    print('onPanUpdate');
    print(details.localPosition);
  },
)
Victor Eronmosele
  • 7,040
  • 2
  • 10
  • 33
Cosmo Dai
  • 335
  • 2
  • 6