How to get widget height?

I don’t understand how LayoutBuilder is used to get widget height.

I need to display a list of widgets and get their height so that I can calculate some special scroll effects. I am developing a package, and other developers provide a widget (I do not control them). I read that LayoutBuilder can be used to get height.

In a very simple case, I tried to wrap the widget in LayoutBuilder.builder and put it on the stack, but I always get minHeight 0.0 and maxHeight INFINITY . Am I using LayoutBuilder incorrectly?

EDIT . It seems that LayoutBuilder is not an option. I found CustomSingleChildLayout , which is almost a solution.

I expanded this delegate, and I was able to get the height of the widget in the getPositionForChild(Size size, Size childSize) . BUT, the first method called Size getSize(BoxConstraints constraints) and as a constraint, I get 0 to INFINITY because I put these CustomSingleChildLayouts in the ListView.

My problem is that SingleChildLayoutDelegate getSize works as if you need to return the height of the view. At that moment I do not know the height of the child. I can only return constraints.smallest (which is 0, height is 0) or constraints.biggest, which is infinite, and the application crashes.

In the docs, he even says:

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

And this is a strange limitation.

+33
source share
2 answers

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

Only one thing to be careful: this context may not exist if the widget is not displayed. Which can cause problems with ListView, because widgets are displayed only if they are potentially visible.

Another problem is that you cannot get the RenderBox widget during the build call, as the widget has not been processed yet.


But I need assembly size! What can I do?

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

But the coolest thing is that they are in another build thread; they are built after ordinary widgets.

This has one very cool meaning: OverlayEntry can have a size that depends on the widgets of the real widget tree.


Good. But doesn't OverlayEntry need to be rebuilt manually?

Yes they do. But there is one more thing to be aware of: the ScrollController passed to Scrollable is similar to the AnimationController listening text.

This means that you can combine AnimatedBuilder with a ScrollController , this will have a great effect to automatically rebuild your widget on a scroll. perfect for this situation, right?


Combine everything into an example:

In the following example, you will see an overlay that follows the widget inside the ListView and has 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( opaque: false, // lambda created to help working with hot-reload builder: (context) => stickyBuilder(context), ); // not possible inside initState SchedulerBinding.instance.addPostFrameCallback((_) { Overlay.of(context).insert(sticky); }); super.initState(); } @override void dispose() { // remove possible overlays on dispose as they would be visible even after [Navigator.push] 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(); }, ); } } 
+55
source

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

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

+1
source

Source: https://habr.com/ru/post/1275910/


All Articles