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.

Before digging into these tricks, we should mention that the recommended way for getting widget size, is the LayoutBuilder widget. This is a great widget and works well, but crucially it measures the parents size, not its own size so its behavior can be a little tricky to grasp and is not always exactly what you need.

If you just want to get the size directly, read on!

Trick #1: Widget Size

In order to get the size of a Widget, you can use the RenderBox.size property:

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

That’s it! Note that because of how Flutter does layout, size will sometimes be zero, and then will size subsequently in a 2nd layout pass. This always happens in the same frame, so it’s usually not a problem, but just be prepared to handle/ignore 0 here.

Trick #2: Widget Size (in a bit)

Occasionally you need to get the size as it will be in the next frame. For example you may be in a button handler, do some action, and you want to know the new size of the view. To do this you can you can wait one frame, and then check. To do that, you must assign your widget a GlobalKey, and then use the handy Future.microtask method:

Future.microtask(() {
  RenderBox rb = key.currentContext.findRenderObject() as RenderBox;
  if (rb?.size != null) {
    callback(size);
  }
});

This code will let you pass it a context, and one frame later, will give you a callback with the widget size. You don’t need this often, but when you do it can really save some headaches (or at least let you ship, until you figure out what you are doing wrong!). This will usually work without the key (just pass context directly) but we’ve seen cases where a context can get invalidated between frames, so a global key is the most bullet-proof approach.

Also, worth noting that Future.microtask can be used to fire any delayed action and is a very handy little tool in its own right.

Trick #3: Position

The final trick is to get the position of the Widget. To do this, you need its context and then use RenderBox.localToGlobal:

RenderBox rb = context.findRenderObject() as RenderBox;
return rb?.localToGlobal(offset ?? Offset.zero);

That will give you a position, relative to the top left corner of the screen plus whatever Offset you provide. You can use this combined with the size of the widget, to determine its center point.

RenderBox rb = context.findRenderObject() as RenderBox;
if(rb != null && size rb.size != Size.zero){
  Offset centerPtInWidgetSpace = Offset(size.width/2, size.height/2);
  Offset centerPtInScreenSpace = rb.localToGlobal(centerPtInLocal); 
  print("Our global center is: $centerPtInScreenSpace);
}

If you are interested, the RenderBox class itself contains a bunch of interesting functions, and it’s probably worthwhile to spend some time with the docs.

Putting it all together…

For our projects we like to wrap this up in a dedicated class called ContextUtils.dart:

import 'package:flutter/material.dart';

class ContextUtils {
  // Takes a key, and in 1 frame, returns the size of the context attached to the key
  static void getFutureSizeFromGlobalKey(GlobalKey key, Function(Size size) callback) {
    Future.microtask(() {
      Size size = getSizeFromContext(key.currentContext);
      if (size != null) {
        callback(size);
      }
    });
  }

  // Shortcut to get the renderBox size from a context
  static Size getSizeFromContext(BuildContext context) {
    RenderBox rb = context.findRenderObject() as RenderBox;
    return rb?.size ?? Size.zero;
  }

  // Shortcut to get the global position of a context
  static Offset getOffsetFromContext(BuildContext context, [Offset offset = null]) {
    RenderBox rb = context.findRenderObject() as RenderBox;
    return rb?.localToGlobal(offset ?? Offset.zero);
  }
}

We hope you find these Tricks (hacks?) useful in your Flutter projects going forward!

shawn.blais

Shawn has worked as programmer and product designer for over 20 years, shipping several games on Steam and Playstation and dozens of apps on mobile. He has extensive programming experience with Dart, C#, ActionScript, SQL, PHP and Javascript and a deep proficiency with motion graphics and UX design. Shawn is currently the Technical Director for gskinner.

@tree_fortress

One Comment

  1. To know the size of a widget, if i wrap its child in a layoutBuilder and get the size from this layoutBuilder. Won’t this size be the widget’s size i am interested in? Just a thought crossed my mind. Felt logic, might delete later.

Leave a Reply

Your email address will not be published. Required fields are marked *