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.
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;
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