35

How to get widget's absolute coordinates on a screen in Flutter?

Or its offset in a parent

Example:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Simple app',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SimpleScreen(
        title: 'Simple screen',
      ),
    );
  }
}

class SimpleScreen extends StatefulWidget {
  final String title;

  SimpleScreen({Key key, this.title}) : super(key: key);

  @override
  _SimpleScreenState createState() => new _SimpleScreenState();
}

class _SimpleScreenState extends State<SimpleScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Container(
          width: 48.0,
          height: 48.0,
          color: Colors.blue,
        ),
      ),
    );
  }
}

Goal is to get Container's offset in parent. If Center's size is 48.0, then Container's offset in Center's parent/root

Condition: you don't know the wrapper layout (it's a library widget), should be flexible, without hardcoded values

Thanks

Ihor Klimov
  • 898
  • 4
  • 15
  • 23
  • That example doesn't help at all. What are you trying to do with this information ? – Rémi Rousselet May 13 '18 at 14:56
  • Hi check out this issue : https://github.com/flutter/flutter/issues/1234 Looks like there's a https://docs.flutter.io/flutter/widgets/CompositedTransformFollower-class.html that mayhelp with this. :) i've not tried it myself though. – behzad.robot May 13 '18 at 18:45
  • @behzad.robot `CompositedTransformFollower` solves the specific problem mentioned in [that GitHub issue](https://github.com/flutter/flutter/issues/1234). It won't get the absolute coordinates of a widget. – jamesdlin Jun 24 '19 at 21:48

3 Answers3

55

You can use this extension I wrote (requires Dart 2.6):

extension GlobalKeyExtension on GlobalKey {
  Rect? get globalPaintBounds {
    final renderObject = currentContext?.findRenderObject();
    final translation = renderObject?.getTransformTo(null).getTranslation();
    if (translation != null && renderObject?.paintBounds != null) {
      final offset = Offset(translation.x, translation.y);
      return renderObject!.paintBounds.shift(offset);
    } else {
      return null;
    }
  }
}

Example how to use it:

final containerKey = GlobalKey();

Container(
  key: containerKey,
  width: 100,
  height: 50,
)

void printWidgetPosition() {
  print('absolute coordinates on screen: ${containerKey.globalPaintBounds}');
}

Also you can see similar solution from Daniel, but using BuildContext (using GlobalKey is relatively expensive).

vovahost
  • 34,185
  • 17
  • 113
  • 116
  • how can I call ```Rect get containerRect => containerKey.globalPaintBounds;``` inside a function. I need to get position of a animated container periodically so I want to call it inside Timer.Periodic ? – Tushar Gautam Sep 30 '20 at 07:42
  • @TusharGautam Just save the extension from my answer to a dart file, then import this file to whatever place you're trying to use `containerKey.globalPaintBounds`. – vovahost Sep 30 '20 at 10:26
  • Hi @vovahost It works for normal widgets but the measurements, especially the `top` measurements become inaccurate if the widgets are in a `ListView`. To be accurate, the measurements become inaccurate after scrolling. – Renegade Jul 11 '22 at 08:07
9

I changed @vovahost extension function, because it's not working in transformed widgets(for example widgets inside RotatedBox). This is working on all widgets.

extension GlobalKeyExtension on GlobalKey {
  Rect? get globalPaintBounds {
    final renderObject = currentContext?.findRenderObject();
    final matrix = renderObject?.getTransformTo(null);

    if (matrix != null && renderObject?.paintBounds != null) {
      final rect = MatrixUtils.transformRect(matrix, renderObject!.paintBounds);
      return rect;
    } else {
      return null;
    }
  }
}
Karen Hovhannisyan
  • 1,140
  • 2
  • 21
  • 31
  • Does it work inside a `ListView` as well? I posted a comment on vovahost's answer about the problem I am facing. The measurements become inaccurate after scrolling in a `ListView` – Renegade Jul 11 '22 at 08:08
7

According to documentation using GlobalKey is relatively expensive. U can use just an Element(BuildContext) to calculate it's global bounds. I improved solution above from @vovahost:

extension GlobalPaintBounds on BuildContext {
  Rect? get globalPaintBounds {
    final renderObject = findRenderObject();
    final translation = renderObject?.getTransformTo(null).getTranslation();
    if (translation != null && renderObject?.paintBounds != null) {
      final offset = Offset(translation.x, translation.y);
      return renderObject!.paintBounds.shift(offset);
    } else {
      return null;
    }
  }
}
Daniel
  • 71
  • 1
  • 2
  • 1
    How do you use this? Can you provide an example? – sagar suri Oct 17 '22 at 15:48
  • @sagarsuri you can call `context.globalPaintBounds` on your instantiated object if it has a `BuildContext context` property. The easiest place to do this would be in the `build` method. – Andy Shephard Nov 25 '22 at 15:52
  • but how would I use this? in the solution above, I can attach a key to every Widget, with this modification, I can't assign a different BuildContext to each Widget? – Cedric Mar 06 '23 at 07:08