How to get height of a Widget?

FlutterDartLayoutWidgetFlutter Widget

Flutter Problem Overview


I don't understand how LayoutBuilder is used to get the height of a Widget.

I need to display the list of Widgets and get their height so I can compute some special scroll effects. I am developing a package and other developers provide widget (I don't control them). I read that LayoutBuilder can be used to get height.

In very simple case, I tried to wrap Widget in LayoutBuilder.builder and put it in the Stack, but I always get minHeight 0.0, and maxHeight INFINITY. Am I misusing the LayoutBuilder?

EDIT: It seems that LayoutBuilder is a no go. I found the CustomSingleChildLayout which is almost a solution.

I extended that delegate, and I was able to get the height of widget in getPositionForChild(Size size, Size childSize) method. BUT, the first method that is called is Size getSize(BoxConstraints constraints) and as constraints, I get 0 to INFINITY because I'm laying these CustomSingleChildLayouts in a ListView.

My problem is that SingleChildLayoutDelegate getSize operates like it needs to return the height of a view. I don't know the height of a child at that moment. I can only return constraints.smallest (which is 0, the height is 0), or constraints.biggest which is infinity and crashes the app.

In the docs it even says:

> ...but the size of the parent cannot depend on the size of the child.

And that's a weird limitation.

Flutter Solutions


Solution 1 - Flutter

To get the size/position of a widget on screen, you can use GlobalKey to get its BuildContext to then find the RenderBox of that specific widget, which will contain its global position and rendered size.

Just one thing to be careful of: That context may not exist if the widget is not rendered. Which can cause a problem with ListView as widgets are rendered only if they are potentially visible.

Another problem is that you can't get a widget's RenderBox during build call as the widget hasn't been rendered yet.


But what if I need to get the size during the build! What can I do?

There's one cool widget that can help: Overlay and its OverlayEntry. They are used to display widgets on top of everything else (similar to stack).

But the coolest thing is that they are on a different build flow; they are built after regular widgets.

That have one super cool implication: OverlayEntry can have a size that depends on widgets of the actual widget tree.


Okay. But don't OverlayEntry requires to be rebuilt manually?

Yes, they do. But there's another thing to be aware of: ScrollController, passed to a Scrollable, is a listenable similar to AnimationController.

Which means you could combine an AnimatedBuilder with a ScrollController, it would have the lovely effect to rebuild your widget automatically on a scroll. Perfect for this situation, right?


Combining everything into an example:

In the following example, you'll see an overlay that follows a widget inside ListView and shares the same height.

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  final controller = ScrollController();
  OverlayEntry sticky;
  GlobalKey stickyKey = GlobalKey();

  @override
  void initState() {
    if (sticky != null) {
      sticky.remove();
    }
    sticky = OverlayEntry(
      builder: (context) => stickyBuilder(context),
    );

    SchedulerBinding.instance.addPostFrameCallback((_) {
      Overlay.of(context).insert(sticky);
    });

    super.initState();
  }

  @override
  void dispose() {
    sticky.remove();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        controller: controller,
        itemBuilder: (context, index) {
          if (index == 6) {
            return Container(
              key: stickyKey,
              height: 100.0,
              color: Colors.green,
              child: const Text("I'm fat"),
            );
          }
          return ListTile(
            title: Text(
              'Hello $index',
              style: const TextStyle(color: Colors.white),
            ),
          );
        },
      ),
    );
  }

  Widget stickyBuilder(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (_,Widget child) {
        final keyContext = stickyKey.currentContext;
        if (keyContext != null) {
          // widget is visible
          final box = keyContext.findRenderObject() as RenderBox;
          final pos = box.localToGlobal(Offset.zero);
          return Positioned(
            top: pos.dy + box.size.height,
            left: 50.0,
            right: 50.0,
            height: box.size.height,
            child: Material(
              child: Container(
                alignment: Alignment.center,
                color: Colors.purple,
                child: const Text("^ Nah I think you're okay"),
              ),
            ),
          );
        }
        return Container();
      },
    );
  }
}

Note:

When navigating to a different screen, call following otherwise sticky would stay visible.

sticky.remove();

Solution 2 - Flutter

This is (I think) the most straightforward way to do this.

Copy-paste the following into your project.

UPDATE: using RenderProxyBox results in a slightly more correct implementation, because it's called on every rebuild of the child and its descendants, which is not always the case for the top-level build() method.

NOTE: This is not exactly an efficient way to do this, as pointed by Hixie here. But it is the easiest.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';


typedef void OnWidgetSizeChange(Size size);

class MeasureSizeRenderObject extends RenderProxyBox {
  Size? oldSize;
  final OnWidgetSizeChange onChange;

  MeasureSizeRenderObject(this.onChange);

  @override
  void performLayout() {
    super.performLayout();

    Size newSize = child!.size;
    if (oldSize == newSize) return;

    oldSize = newSize;
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      onChange(newSize);
    });
  }
}

class MeasureSize extends SingleChildRenderObjectWidget {
  final OnWidgetSizeChange onChange;

  const MeasureSize({
    Key? key,
    required this.onChange,
    required Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return MeasureSizeRenderObject(onChange);
  }
}

Then, simply wrap the widget whose size you would like to measure with MeasureSize.

var myChildSize = Size.zero;

Widget build(BuildContext context) {
  return ...( 
    child: MeasureSize(
      onChange: (size) {
        setState(() {
          myChildSize = size;
        });
      },
      child: ...
    ),
  );
}

So yes, the size of the parent cannot can depend on the size of the child if you try hard enough.


Personal anecdote - This is handy for restricting the size of widgets like Align, which likes to take up an absurd amount of space.

Solution 3 - Flutter

Here's a sample on how you can use LayoutBuilder to determine the widget's size.

Since LayoutBuilder widget is able to determine its parent widget's constraints, one of its use case is to be able to have its child widgets adapt to their parent's dimensions.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  var dimension = 40.0;

  increaseWidgetSize() {
    setState(() {
      dimension += 20;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(children: <Widget>[
          Text('Dimension: $dimension'),
          Container(
            color: Colors.teal,
            alignment: Alignment.center,
            height: dimension,
            width: dimension,
            // LayoutBuilder inherits its parent widget's dimension. In this case, the Container in teal
            child: LayoutBuilder(builder: (context, constraints) {
              debugPrint('Max height: ${constraints.maxHeight}, max width: ${constraints.maxWidth}');
              return Container(); // create function here to adapt to the parent widget's constraints
            }),
          ),
        ]),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: increaseWidgetSize,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Demo

demo

Logs

I/flutter (26712): Max height: 40.0, max width: 40.0
I/flutter (26712): Max height: 60.0, max width: 60.0
I/flutter (26712): Max height: 80.0, max width: 80.0
I/flutter (26712): Max height: 100.0, max width: 100.0

Update: You can also use MediaQuery to achieve similar function.

@override
Widget build(BuildContext context) {
  var screenSize = MediaQuery.of(context).size ;
  if (screenSize.width > layoutSize){
    return Widget(); 
  } else {
    return Widget(); /// Widget if doesn't match the size
  }
}

Solution 4 - Flutter

Let me give you a widget for that


class SizeProviderWidget extends StatefulWidget {
  final Widget child;
  final Function(Size) onChildSize;

  const SizeProviderWidget(
      {Key? key, required this.onChildSize, required this.child})
      : super(key: key);
  @override
  _SizeProviderWidgetState createState() => _SizeProviderWidgetState();
}

class _SizeProviderWidgetState extends State<SizeProviderWidget> {
  @override
  void initState() {
    ///add size listener for first build
    _onResize();
    super.initState();
  }

  void _onResize() {
    WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
      if (context.size is Size) {
        widget.onChildSize(context.size!);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    ///add size listener for every build uncomment the fallowing
    ///_onResize();
    return widget.child;
  }
}

EDIT Just wrap the SizeProviderWidget with OrientationBuilder to make it respect the orientation of the device

Solution 5 - Flutter

If I understand correctly, you want to measure the dimension of some arbitrary widgets, and you can wrap those widgets with another widget. In that case, the method in the this answer should work for you.

Basically the solution is to bind a callback in the widget lifecycle, which will be called after the first frame is rendered, from there you can access context.size. The catch is that you have to wrap the widget you want to measure within a stateful widget. And, if you absolutely need the size within build() then you can only access it in the second render (it's only available after the first render).

Solution 6 - Flutter

findRenderObject() returns the RenderBox which is used to give the size of the drawn widget and it should be called after the widget tree is built, so it must be used with some callback mechanism or addPostFrameCallback() callbacks.

class SizeWidget extends StatefulWidget {
  @override
  _SizeWidgetState createState() => _SizeWidgetState();
}

class _SizeWidgetState extends State<SizeWidget> {
  final GlobalKey _textKey = GlobalKey();
  Size textSize;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => getSizeAndPosition());
  }

  getSizeAndPosition() {
    RenderBox _cardBox = _textKey.currentContext.findRenderObject();
    textSize = _cardBox.size;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Size Position"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Text(
            "Currern Size of Text",
            key: _textKey,
            textAlign: TextAlign.center,
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          SizedBox(
            height: 20,
          ),
          Text(
            "Size - $textSize",
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}

Output:

enter image description here

Solution 7 - Flutter

There is no direct way to calculate the size of the widget, so to find that we have to take the help of the context of the widget.

Calling context.size returns us the Size object, which contains the height and width of the widget. context.size calculates the render box of a widget and returns the size.

Checkout https://medium.com/flutterworld/flutter-how-to-get-the-height-of-the-widget-be4892abb1a2

Solution 8 - Flutter

> Might be this could help > > Tested on Flutter: 2.2.3

Copy Below code this in your project.

 import 'package:flutter/material.dart';
    import 'package:flutter/scheduler.dart';
    
    class WidgetSize extends StatefulWidget {
      final Widget child;
      final Function onChange;
    
      const WidgetSize({
        Key? key,
        required this.onChange,
        required this.child,
      }) : super(key: key);
    
      @override
      _WidgetSizeState createState() => _WidgetSizeState();
    }
    
    class _WidgetSizeState extends State<WidgetSize> {
      @override
      Widget build(BuildContext context) {
        SchedulerBinding.instance!.addPostFrameCallback(postFrameCallback);
        return Container(
          key: widgetKey,
          child: widget.child,
        );
      }
    
      var widgetKey = GlobalKey();
      var oldSize;
    
      void postFrameCallback(_) {
        var context = widgetKey.currentContext;
        if (context == null) return;
    
        var newSize = context.size;
        if (oldSize == newSize) return;
    
        oldSize = newSize;
        widget.onChange(newSize);
      }
    }

declare a variable to store Size

Size mySize = Size.zero;

Add following code to get the size:

 child: WidgetSize(
          onChange: (Size mapSize) {
            setState(() {
              mySize = mapSize;
               print("mySize:" + mySize.toString());
             });
             },
         child: ()

Solution 9 - Flutter

use the package: z_tools. The steps:

1. change main file
void main() async {
  runZoned(
    () => runApp(
      CalculateWidgetAppContainer(
        child: Center(
          child: LocalizedApp(delegate, MyApp()),
        ),
      ),
    ),
    onError: (Object obj, StackTrace stack) {
      print('global exception: obj = $obj;\nstack = $stack');
    },
  );
}
2. use in function
_Cell(
    title: 'cal: Column-min',
    callback: () async {
        Widget widget1 = Column(
        mainAxisSize: MainAxisSize.min,
        children: [
            Container(
            width: 100,
            height: 30,
            color: Colors.blue,
            ),
            Container(
            height: 20.0,
            width: 30,
            ),
            Text('111'),
        ],
        );
        // size = Size(100.0, 66.0)
        print('size = ${await getWidgetSize(widget1)}');
    },
),

Solution 10 - Flutter

The easiest way is to use MeasuredSize it's a widget that calculates the size of it's child in runtime.

You can use it like so:

MeasuredSize(
          onChange: (Size size) {
            setState(() {
              print(size);
            });
          },
          child: Text(
            '$_counter',
            style: Theme.of(context).textTheme.headline4,
          ),
        );

You can find it here: https://pub.dev/packages/measured_size

Solution 11 - Flutter

It's easy and still can be done in StatelessWidget.

class ColumnHeightWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final scrollController = ScrollController();
    final columnKey = GlobalKey();

    _scrollToCurrentProgress(columnKey, scrollController);

    return Scaffold(
      body: SingleChildScrollView(
        controller: scrollController,
        child: Column(
          children: [],
        ),
      ),
    );
  }

  void _scrollToCurrentProgress(GlobalKey<State<StatefulWidget>> columnKey,
      ScrollController scrollController) {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      final RenderBox renderBoxRed =
          columnKey.currentContext.findRenderObject();
      final height = renderBoxRed.size.height;
      scrollController.animateTo(percentOfHeightYouWantToScroll * height,
          duration: Duration(seconds: 1), curve: Curves.decelerate);
    });
  }
}

in the same manner you can calculate any widget child height and scroll to that position.

Solution 12 - Flutter

I made this widget as a simple stateless solution:

class ChildSizeNotifier extends StatelessWidget {
  final ValueNotifier<Size> notifier = ValueNotifier(const Size(0, 0));
  final Widget Function(BuildContext context, Size size, Widget child) builder;
  final Widget child;
  UjChildSizeNotifier({
    Key key,
    @required this.builder,
    this.child,
  }) : super(key: key) {}

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback(
      (_) {
        notifier.value = (context.findRenderObject() as RenderBox).size;
      },
    );
    return ValueListenableBuilder(
      valueListenable: notifier,
      builder: builder,
      child: child,
    );
  }
}

Use it like this

ChildSizeNotifier(
  builder: (context, size, child) {
    // size is the size of the text
    return Text(size.height > 50 ? 'big' : 'small');
  },
)

Solution 13 - Flutter

**Credit to @Manuputty**    

class OrigChildWH extends StatelessWidget {
  final Widget Function(BuildContext context, Size size, Widget? child) builder;
  final Widget? child;
  const XRChildWH({
    Key? key,
    required this.builder,
    this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return OrientationBuilder(builder: (context, orientation) {
      return ChildSizeNotifier(builder: builder);
    });
  }
}

class ChildSizeNotifier extends StatelessWidget {
  final ValueNotifier<Size> notifier = ValueNotifier(const Size(0, 0));
  final Widget Function(BuildContext context, Size size, Widget? child) builder;
  final Widget? child;
  ChildSizeNotifier({
    Key? key,
    required this.builder,
    this.child,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance!.addPostFrameCallback(
      (_) {
        notifier.value = (context.findRenderObject() as RenderBox).size;
      },
    );
    return ValueListenableBuilder(
      valueListenable: notifier,
      builder: builder,
      child: child,
    );
  }
}

**Simple to use:**

OrigChildWH(
  builder: (context, size, child) {
    //Your child here: mine:: Container()
    return Container()
  })

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJoKrView Question on Stackoverflow
Solution 1 - FlutterRémi RousseletView Answer on Stackoverflow
Solution 2 - FlutterDev AggarwalView Answer on Stackoverflow
Solution 3 - FlutterOmattView Answer on Stackoverflow
Solution 4 - FlutterYaduView Answer on Stackoverflow
Solution 5 - FlutterGan QuanView Answer on Stackoverflow
Solution 6 - FlutterJitesh MohiteView Answer on Stackoverflow
Solution 7 - FlutterKalpesh KhandlaView Answer on Stackoverflow
Solution 8 - FlutterAnup GuptaView Answer on Stackoverflow
Solution 9 - FlutterqqzhaoView Answer on Stackoverflow
Solution 10 - FlutterAyham OrfaliView Answer on Stackoverflow
Solution 11 - FlutterDrashyrView Answer on Stackoverflow
Solution 12 - FlutterCoda VetoView Answer on Stackoverflow
Solution 13 - FlutterXploitsRView Answer on Stackoverflow