我不明白如何LayoutBuilder用于获取小部件的高度。
LayoutBuilder
我需要显示小部件列表并获取它们的高度,以便计算一些特殊的滚动效果。我正在开发一个包,其他开发人员提供小部件(我不控制它们)。我读到 LayoutBuilder 可以用来获取高度。
在非常简单的情况下,我尝试将 Widget 包装在 LayoutBuilder.builder 中并将其放入 Stack 中,但我总是得到minHeight 0.0, 和maxHeight INFINITY. 我在滥用 LayoutBuilder 吗?
minHeight
0.0
maxHeight
INFINITY
编辑 :似乎 LayoutBuilder 是不行的。我发现CustomSingleChildLayout几乎是一个解决方案。
我扩展了该委托,并且能够在getPositionForChild(Size size, Size childSize)方法中获取小部件的高度。但是,调用的第一个方法是Size getSize(BoxConstraints constraints)作为约束,我得到 0 到 INFINITY,因为我将这些 CustomSingleChildLayouts 放置在 ListView 中。
getPositionForChild(Size size, Size childSize)
Size getSize(BoxConstraints constraints)
我的问题是 SingleChildLayoutDelegate 的getSize操作就像它需要返回视图的高度一样。我不知道那个时候孩子的身高。我只能返回constraints.smallest(即0,高度为0),或constraints.biggest,它是无穷大并使应用程序崩溃。
getSize
在文档中它甚至说:
…但是父母的大小不能取决于孩子的大小。
这是一个奇怪的限制。
要获取屏幕上小部件的大小/位置,您可以使用GlobalKey它来获取它BuildContext,然后找到该RenderBox特定小部件的,其中将包含其全局位置和渲染大小。
GlobalKey
BuildContext
RenderBox
只需要注意一件事:如果未呈现小部件,则该上下文可能不存在。这可能会导致问题,ListView因为小部件仅在它们可能可见时才会呈现。
ListView
RenderBox另一个问题是您在调用期间无法获取小部件,build因为小部件尚未呈现。
build
但是如果我需要在构建过程中获取大小怎么办! 我能做些什么?
有一个很酷的小部件可以提供帮助:Overlay及其OverlayEntry. 它们用于在其他所有内容之上显示小部件(类似于堆栈)。
Overlay
OverlayEntry
但最酷的是它们处于不同的build流程中。它们是 在 常规小部件之后构建的。
这有一个非常酷的含义:OverlayEntry可以具有取决于实际小部件树的小部件的大小。
好的。 但是OverlayEntry不需要手动重建吗?
是的,他们有。但是还有一点需要注意:ScrollController传递给 a 的Scrollable,是一个可听的,类似于AnimationController.
ScrollController
Scrollable
AnimationController
这意味着您可以将 aAnimatedBuilder与 a结合使用ScrollController,它会产生在滚动上自动重建小部件的可爱效果。非常适合这种情况,对吧?
AnimatedBuilder
将所有内容组合成一个示例:
在以下示例中,您将看到一个覆盖在内部小部件之后ListView并共享相同高度。
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(); }, ); } }
注意 :
当导航到不同的屏幕时,呼叫跟随否则粘滞将保持可见。
sticky.remove();