如何获得Widget的高度?

时间:2018-03-15 19:22:29

标签: flutter

我不明白如何使用LayoutBuilder来获取Widget的高度。

我需要显示小部件列表并获得它们的高度,以便我可以计算一些特殊的滚动效果。我正在开发一个包,其他开发人员提供小部件(我不控制它们)。我读到LayoutBuilder可以用来获得高度。

在非常简单的情况下,我尝试在LayoutBuilder.builder中包装Widget并将其放入Stack中,但我始终得到minHeight 0.0maxHeight INFINITY 。我是否滥用了LayoutBuilder?

编辑:看来LayoutBuilder似乎不行。我发现CustomSingleChildLayout几乎是一个解决方案。

我扩展了该委托,我能够在getPositionForChild(Size size, Size childSize)方法中获得小部件的高度。但是,第一个调用的方法是Size getSize(BoxConstraints constraints),作为约束,我得到0到INFINITY,因为我在ListView中放置了这些CustomSingleChildLayouts。

我的问题是SingleChildLayoutDelegate getSize的运行方式就像需要返回视图的高度一样。那时我还不知道孩子的身高。我只能返回constraints.smallest(为0,高度为0)或constraints.biggest,这是无穷大并崩溃应用程序。

在文档中它甚至说:

  

...但是父母的大小不能取决于孩子的大小。

这是一个奇怪的限制。

13 个答案:

答案 0 :(得分:45)

(我认为)这是最简单的方法。

将以下内容复制并粘贴到您的项目中。

更新:使用RenderProxyBox会导致实现更正确,因为在每次对其子代及其子代进行重新构建时都会调用它,而对于顶层build()方法并不总是这样。

注意:正如Hixie here所指出的,这并不是一种有效的方法。但这是最简单的。

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);
  }
}

然后,只需包装要用MeasureSize测量尺寸的小部件。


var myChildSize = Size.zero;

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

因此,是的,如果您努力尝试,那么父母的大小 可以取决于孩子的大小。


个人轶事-这对于限制Align之类的小部件的大小非常方便,它喜欢占用大量的空间。

答案 1 :(得分:41)

要在屏幕上显示小部件的大小/位置,您可以使用GlobalKey获取其BuildContext,然后找到该特定小部件的RenderBox,其中包含其全局位置和渲染大小。

只需要注意一件事:如果未呈现窗口小部件,则该上下文可能不存在。这可能会导致ListView出现问题,因为只有在窗口小部件可能可见时才会呈现它们。

另一个问题是,由于小部件尚未呈现,因此您无法在RenderBox来电期间获取小工具build

但是在构建期间我需要大小!我该怎么办?

有一个很酷的小部件可以提供帮助:Overlay及其OverlayEntry。 它们用于在其他所有内容上显示小部件(类似于堆栈)。

但最酷的是他们处于不同的build流程;它们是在常规小部件后构建的

这有一个非常酷的含义:OverlayEntry的大小取决于实际小部件树的小部件。

好。但是不要OverlayEntry需要手动重建吗?

是的,他们这样做。但是还有另一件需要注意的事情:传递给ScrollController的{​​{1}}是类似于Scrollable的可听类。

这意味着您可以将AnimationControllerAnimatedBuilder结合使用,它可以在滚动条上自动重建窗口小部件。对这种情况很好,对吧?

将所有内容组合成一个示例:

在以下示例中,您将看到ScrollController内的窗口小部件后面的叠加层,并且共享相同的高度。

ListView

答案 2 :(得分:3)

以下是如何使用LayoutBuilder确定小部件尺寸的示例。

由于LayoutBuilder小部件能够确定其父小部件的约束,因此其用例之一就是能够使其子小部件适应其父级的尺寸。

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

日志

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

答案 3 :(得分:2)

没有直接的方法可以计算widget的大小,所以要找到我们必须借助widget的上下文。

调用 context.size 会返回 Size 对象,其中包含小部件的高度和宽度。 context.size 计算小部件的渲染框并返回大小。

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

答案 4 :(得分:1)

如果我理解正确,那么您想测量一些任意小部件的尺寸,然后可以将这些小部件包装到另一个小部件中。在这种情况下,this answer中的方法应该对您有用。

基本上,解决方案是在小部件生命周期中绑定一个回调,该回调将在呈现第一帧之后调用,从那里您可以访问context.size。要注意的是,您必须将要测量的小部件包装在有状态的小部件中。而且,如果您绝对需要build()之内的大小,则只能在第二个渲染中访问它(仅在第一个渲染之后才可用)。

答案 5 :(得分:1)

让我给你一个小部件

set FLASK_APP=flask_app 

这里发生的是,在任何窗口小部件中,一旦构建了窗口小部件,都可以使用size变量,因此,通过将一个函数传递给上述窗口小部件,将在您使用已构建窗口小部件的大小构建窗口小部件后立即调用该函数可以使用。


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

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

class _SizeProviderWidgetState extends State<SizeProviderWidget> {

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      widget.onChildSize(context.size);
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

编辑 只需将SizeProviderWidget( child:MyWidget(),//the widget we want the size of, onChildSize:(size){ //the size of the rendered MyWidget() is available here } ) SizeProviderWidget包裹起来,使其尊重设备的方向

答案 6 :(得分:0)

findRenderObject()返回RenderBox,该值用于给出绘制的窗口小部件的大小,并且应在构建窗口小部件树之后调用它,因此它必须与某种回调机制或{{ 1}}回调。

addPostFrameCallback()

输出:

enter image description here

答案 7 :(得分:0)

使用软件包:z_tools。 步骤:

1。更改主文件

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。在功能中使用

_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)}');
    },
),

答案 8 :(得分:0)

最简单的方法是使用MeasuredSize,它是一个小部件,可在运行时计算其子项的大小。

您可以像这样使用它:

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

您可以在这里找到它:https://pub.dev/packages/measured_size

答案 9 :(得分:0)

这很容易,而且仍然可以在 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);
    });
  }
}

以同样的方式,您可以计算任何小部件子​​高度并滚动到该位置。

答案 10 :(得分:0)

我将这个小部件作为一个简单的无状态解决方案:

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,
    );
  }
}

像这样使用

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

答案 11 :(得分:0)

<块引用>

这可能会有所帮助

在 Flutter 上测试:2.2.3

在您的项目中复制以下代码。

 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);
      }
    }

声明一个变量来存储Size

Size mySize = Size.zero;

添加以下代码以获取大小:

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

答案 12 :(得分:-1)

<p>@override
<br/>
  Widget build(BuildContext context) {<br/>
   &nbsp;&nbsp; &nbsp;return Container(<br/>
     &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; width: MediaQuery.of(context).size.width,<br/>
     &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; height: MediaQuery.of(context).size.height,<br/>
    &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; color: Colors.blueAccent,<br/>
   &nbsp;&nbsp; &nbsp;);<br/>
  }</p>