Flutter How to get widget height, width, x, and y position

eye-catch Dart and Flutter

If we can use a variable to set rendering info like height, width, x, and position, we can use the info to do something for another widget. However, that info is often set automatically. How can we get it in this case?

You can check the completed code in my GitHub repository.

Sponsored links

Conclusion first if you don’t have enough time

If you just want to know how to implement it, this is the result.

final globalKey = GlobalKey();

final renderBox = globalKey.currentContext?.findRenderObject() as RenderBox;
Offset position = renderBox.localToGlobal(Offset.zero);
final height = renderBox.size.height;
final width = renderBox.size.width;
final left = position.dx;
final top = position.dy;
Sponsored links

The target widget

First, let’s check which info we are going to get. See the following image.

There are two containers and red rectangle is what we want. It is generated by Container widget and it sets both margin and padding.

The view implementation is the following without getting the widget info.

import 'package:flutter/material.dart';

class GetWidgetInfo extends StatefulWidget {
  const GetWidgetInfo({Key? key}) : super(key: key);

  @override
  _GetWidgetInfoState createState() => _GetWidgetInfoState();
}

class _GetWidgetInfoState extends State<GetWidgetInfo> {
  final containerKey = GlobalKey();
  double? height;
  double? width;
  double? top;
  double? bottom;
  double? left;
  double? right;
pic1
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Get Widget Info'),
      ),
      body: Column(
        children: <Widget>[
          Container(
            margin: const EdgeInsets.all(10),
            padding: const EdgeInsets.all(10),
            decoration: BoxDecoration(
              border: Border.all(
                width: 2,
                color: Colors.black,
              ),
            ),
            child: Container(
              key: containerKey,
              margin: const EdgeInsets.all(20),
              padding: const EdgeInsets.all(10),
              height: 100,
              width: double.infinity,
              decoration: BoxDecoration(
                  border: Border.all(
                width: 2,
                color: Colors.redAccent,
              )),
            ),
          ),
          TextButton(
              child: Text("Press here to show the info"),
              onPressed: () {
                // get widget info here
              }),
          Text("top: ${top}"),
          Text("bottom: ${bottom}"),
          Text("left: ${left}"),
          Text("right: ${right}"),
          Text("height: ${height}"),
          Text("width: ${width}"),
        ],
      ),
    );
  }
}

Define a GlobalKey for the desired widget

Widget info can be accessed via GlobalKey. The key needs to be set to the desired widget. All widgets have key property. So, set the GlobalKey to key.

final containerKey = GlobalKey();

@override
  Widget build(BuildContext context) {
    Container(key: containerKey,
        ...
  }

Get the widget info via RenderBox

GlobalKey has currentContext property. To get RenderBox of the widget, we can call keyForSlider.currentContext?.findRenderObject(). However, as you can see it, currentContext is nullable.

The widget render info can be extracted after the widget is actually rendered. It means that currentContext is null in build method. Therefore, it needs to be used in a callback function.

In this example, we use it in onPressed callback here. When it’s called, build method is already completed its work and it knows the widget position and size.

TextButton(
    child: Text("Press here to show the info"),
    onPressed: () {
    // get widget info here
    }),
    ...

The info can be read in the following way.

TextButton(
  child: Text("Press here to show the info"),
  onPressed: () {
    final renderBox = containerKey.currentContext?.findRenderObject() as RenderBox;
    final position = renderBox.localToGlobal(Offset.zero);
    setState(() {
      height = renderBox.size.height;
      width = renderBox.size.width;
      left = position.dx;
      top = position.dy;
      right = position.dx + renderBox.size.width;
      bottom = position.dy + renderBox.size.height;
    });
  }),

Position dx and dy doesn’t consider marge and padding

If we really need the position where the rectangle is actually rendered, it needs to be calculated by ourselves. position.dx and position.dy don’t consider the marge and padding.

How to handle screen resize event

The screen size changes if a phone is rotated or simply the edge of the window is dragged and moved on a desktop. We need to add the widget itself to the observer by using WidgetsBindingObserver. It is added with with keyword. This is called mixin that allows us to reuse the code in multiple places. Then, we can add logic to didChangeMetrics.

class _GetWidgetInfoState extends State<GetWidgetInfo> with WidgetsBindingObserver {
  ...

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeMetrics() {
    _updateData();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  ...

  void _updateData() {
    final renderBox = containerKey.currentContext?.findRenderObject() as RenderBox;
    final position = renderBox.localToGlobal(Offset.zero);
    setState(() {
      height = renderBox.size.height;
      width = renderBox.size.width;
      left = position.dx;
      top = position.dy;
      right = position.dx + renderBox.size.width;
      bottom = position.dy + renderBox.size.height;
    });
  }

I extracted the info update logic into a method. The info is updated correctly when the screen is resized.

Don’t forget to implement dispose method! I didn’t implement it first and it started throwing the following error when I went back to the home view and returned to this view.

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type 'Null' is not a subtype of type 'RenderBox' in type cast
#0      _GetWidgetInfoState._updateData
package:flutter_samples/get_widget_info.dart:78
#1      _GetWidgetInfoState.didChangeMetrics
package:flutter_samples/get_widget_info.dart:28
#2      WidgetsBinding.handleMetricsChanged
package:flutter/…/widgets/binding.dart:550
#3      _invoke (dart:ui/hooks.dart:148:13)
#4      PlatformDispatcher._updateWindowMetrics (dart:ui/platform_dispatcher.dart:246:5)
#5      _updateWindowMetrics (dart:ui/hooks.dart:33:31)

Because a different GlobalKey is created when the view is shown again. Then, the second key is added to the observer but didChangeMetrics is called with the previous context. The widget in the previous context doesn’t exist anymore and thus it throws an error.

Note that the size info is actually not accurate. The info is from one event back. You know what I mean when you see the following video. The info is updated when clicking the text button.

Comments

Copied title and URL