Flutter: How to measure Widgets

One of the trickier things to do in Flutter is to get the size of things.

Generally there are 3 use cases you might have:

  1. Measure your own context
  2. Measure your parents context
  3. Measure a child context

The first 2 are quite simple so we’ll just skim them. The 3rd is more interesting.

Measure your own size

To get your own size, you can just do:

Size s = (context.findRenderObject() as RenderBox)?.size ?? Size.zero;

Note: The first time this runs the size will be zero, Flutter needs to layout each Widget twice before we can get an accurate size.

Measure your Parents size

As most developers probably know, you can use a LayoutBuilder to get the size of your parent. Not a lot to say about this one, it’s primarily useful when you want to act in some responsive way, but rather than using full-screen dimensions (MediaQuery), you want to use the size available to the Widget.

LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
     // constraints.maxWidth and constraints.maxHeight are your parent's size.
  }
}

For example, on Web and Desktop, you will use LayoutBuilder almost exclusively over MediaQuery, as most Widgets exist in a panel, pop-over or some other partial-view ui. Use this when you don’t want to layout according to the entire window size, but instead to the size of the actual panel.

For example, you may have a component that will switch to a Row when Horizontal or Column when vertical. This component wouldn’t care at all about the size of the entire window, it only cares about the room available to itself to grow.

Measure a Childs size

Measuring a child Widget in Flutter is a little more complex, but it’s certainly doable. The basic idea, is to use the “own size” approach from above but dispatch that size to any listeners.

The KISS approach here, is a simple MeasurableWidget with a callback:

class MeasurableWidget extends StatefulWidget {
  const MeasurableWidget({Key key, this.child, this.onSized}) : super(key: key);
  final Widget child;
  final void Function(Size size) onSized;

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

class _MeasurableWidgetState extends State<MeasurableWidget> {
  bool_hasMeasured = false;
  @override
  Widget build(BuildContext context) {
    Size size = (context.findRenderObject() as RenderBox)?.size ?? Size.zero;
    if (size != Size.zero) {
      widget.onSized?.call(size);
    } else if (!_hasMeasured) {
      // Need to build twice in order to get size
      scheduleMicrotask(() => setState(()=>_hasMeasured = true));
    }
    return widget.child;
  }
}

Due to how Flutter manages layout by default, we have to render twice in order to get a valid size. This is not ideal, but also 2 renders is nothing to worry about generally.

To use, you can just do:

MeasurableWidget(child: AnyRandomWidget(), onSized: _handleWidgetSized)

In practice, you might use this sort of thing to manually Align or Offset some child widget:

Size _widgetSize = Size.zero;
Widget build(BuildContext context){
   Offset o = Offset(_widgetSize.size.width/2, _widgetSize.size.height/2);
   return Transform.translate(
      offset: o, 
      child: MeasurableWidget(child: ..., onSized: _handleWidgetSized);
   );
}

void _handleWidgetSized(Size value) => setState(()=>_widgetSize = value);

This class can then center any Widget that was passed into it, even though it knows nothing about that Widget. It just wraps it in a MeasurableWidget and waits for the callback!

Using Notifications

Another approach to this can be done with Flutter Notifications. Notifications are great because they bubble upwards, sort of like how Provider drills objects downwards, but in the other direction.

This approach is handy if you need your size to travel up multiple tiers of the application, and you don’t want to have to continually pass the Size. To do this, you would just create some custom Notification:

class WidgetMeasuredNotification extends Notification {
    WidgetMeasuredNotification(size);
    final Size size;
}

You could then create a widget that dispatches a Notification, instead of using a callback:

class _MeasurableWidgetState extends State<MeasurableWidget> {
  Widget build(BuildContext context) {
    ...
    if (size != Size.zero) {
      WidgetMeasuredNotification(size).dispatch(context);
    } else if (_buildCount == 0) {
    ...
  }
}

The final step is to just catch the Notification with a NotificationListener. We could re-write the usage example above like:

Size _widgetSize = Size.zero;
Widget build(BuildContext context){
   Offset o = Offset(_widgetSize.size.width/2, _widgetSize.size.height/2);
   return NotificationListener(
      child: MeasurableWidget(child: widget.someChild),
      onNotification => (WidgetMeasuredNotification n){
         _handleWidgetSized(n.size);
         return true;
      }
   );
}

void Function(Size value) _handleWidgetSized => setState(()=>_widgetSize = value)

That’s it! Now you have the same setup, but there is no direct coupling between the child Widget, and the Listener. You could have the Listener be multiple levels above the child, and it would still catch the size and can act on it.

You might use this sort of approach in a complex animated menu, that has some visual indicator tied to the size of the selected button. In this case you would want some sort of loose coupling between the MenuController, and the lower-level MenuButtons that are selected.

Going further with RenderBox

So, you might be looking at the above approaches, and thinking to yourself… Measure twice? Come on man!

Come On Man GIFs | Tenor
Can’t we do better?

Yes we can! Using a custom RenderObject and overriding the performLayout method, we can measure a widget in one pass!

First you make a custom RenderBox like so:

class MeasureSizeRenderObject extends RenderProxyBox {
  MeasureSizeRenderObject(this.onChange);
  void Function(Size size) onChange;

  Size _prevSize;
  @override
  void performLayout() {
    super.performLayout();
    Size newSize = child.size;
    if (_prevSize == newSize) return;
    _prevSize = newSize;
    WidgetsBinding.instance.addPostFrameCallback((_) => onChange(newSize));
  }
}

You then make a tiny StatelessWidget wrapper so you can use it:

class MeasurableWidget extends SingleChildRenderObjectWidget {
  const MeasurableWidget({Key key, @required this.onChange, @required Widget child}) : super(key: key, child: child);
  final void Function(Size size) onChange;
  @override
  RenderObject createRenderObject(BuildContext context) => MeasureSizeRenderObject(onChange);
}

With those two small classes in place, you can simple do:

MeasureSize(onChange: _handlePopOverSized, child: ...)

Hopefully this helps you in your Flutter journey and spawns a few new ideas!

Flokk – How we built a Desktop App Using Flutter

Earlier this year Google and Ubuntu approached us with an open brief, to try and create a full-scale desktop application for Linux, macOS, Web, and Windows. The result was Flokk, which we released (and open-sourced) back in July.

In this post, we’re going to dive into some of the challenges we faced, the discoveries we made, and desktop-specific features that we added.

Continue reading →

Flutter Tricks: Widget Size & Position

Occasionally in Flutter, you will need to get the size and/or position of a widget. Some common use cases for this are:

  • You need to measure something and make some decision based on that. For example, you might switch from an expanded column, to a scrolling column, at a certain height threshold.
  • You may want enable or disable a scrollbar based on the height of some content
  • The parent needs to know your position. For example if when you tap a menu button, the parent wants to animate a dot to that position, we need the button to be able to report its position somehow.
Continue reading →

Flutter: Extending State‹T›

Recently we’ve been exploring the ability to extend the base State<T> class, to add additional capabilities to our views, or writing our own custom Mixins to do the same.

Most Developers are familiar with using the various framework Mixins, like TickerProviderStateMixin but what is not commonly known, is how easy it is to create your own flavors of these. Additionally, many developers extend State<T> constantly, and write very repetitive boilerplate code, without realizing how easy it is to create their own BaseState<T> instead.

In this example, we’ll make a “base state” that provides 3 AnimatorControllers to any widget that needs to animate something. We could then apply this to any Widget in our application, and it automatically get 3 animators to play with. No setup, or teardown required.

Continue reading →

A Dive Into the CAMP Site Assets

The site we made for the 2019 CAMP Festival for creatives recently won an Applied Arts award for Community Promotional Design! To celebrate, we looked back at some of the assets that went into the site experience.

The Site

The site took visitors on a mini-journey through a Canadian-inspired landscape. This is the breakdown of the assets for the site header rendered in Blender.
Continue reading →

Export Responsive Layouts for Flutter with Adobe XD

I’m very excited about the v1.0 release of the “XD to Flutter” plugin for Adobe XD. While the prerelease versions were interesting, and occasionally handy for grabbing a style or shape, the addition of responsive layout support in 1.0 makes it a genuinely useful tool for creating beautifully designed widgets and even simple views.

Now that the first production release is available, I thought I’d write up a short blog post that introduces the plugin, and helps you get started using it.

If you’d prefer not to read, you can check out this Adobe Creative Cloud video on LinkedIn that features Will Larche from Google describing what Flutter is, and me (@~17:00) talking about the XD to Flutter plugin.

Continue reading →

Blender Grease Pencil: Creating 3D Environment Illustrations

I saw some Blender Grease Pencil animations by Dedouze and got inspired by what the tool seemed to offer. The Grease Pencil tool is like a typical drawing tool made for Blender. It allows you to draw in 3D space as well as make and play animations in real-time. Pretty neat right? The closest thing I can compare it to is one of those 3D pens that lets you “draw” in a 3D space.

Continue reading →