我不明白如何使用LayoutBuilder来获取Widget的高度.
我需要显示小部件列表并获得它们的高度,以便我可以计算一些特殊的滚动效果.我正在开发一个包,其他开发人员提供小部件(我不控制它们).我读到LayoutBuilder可以用来获得高度.
在非常简单的情况下,我试图在LayoutBuilder.builder中包装Widget并将其放入Stack中,但我总是得到minHeight 0.0,并且maxHeight 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,这是无穷大并崩溃应用程序.
在文档中它甚至说:
...但是父母的大小不能取决于孩子的大小.
这是一个奇怪的限制.
Rém*_*let 65
要在屏幕GlobalKey上获取窗口小部件的大小/位置,您可以使用它BuildContext来查找RenderBox特定窗口小部件,该窗口小部件将包含其全局位置和渲染大小.
需要注意的一件事是:如果未呈现窗口小部件,则可能不存在该上下文.这可能会导致ListView出现问题,因为窗口小部件只有在可能可见时才会呈现.
另一个问题是,由于窗口小部件尚未呈现,因此您无法RenderBox在build调用期间获取窗口小部件.
但是在构建期间我需要大小!我能做什么?
有一个很酷的小部件可以帮助:Overlay和它OverlayEntry.它们用于在其他所有内容上显示小部件(类似于堆栈).
但最酷的是他们处于不同的build流程; 它们是在常规小部件之后构建的.
这有一个超酷的含义:OverlayEntry可以有一个大小取决于实际小部件树的小部件.
好的.但是不要OverlayEntry需要手动重建吗?
是的,他们这样做.但还有另一件事需要注意:ScrollController传递给a Scrollable,是一种类似的可听AnimationController.
这意味着您可以将a AnimatedBuilder与a 组合在一起ScrollController,它可以在滚动条上自动重建窗口小部件.适合这种情况,对吗?
将所有内容组合成一个示例:
在下面的示例中,您将看到内部窗口小部件后面的叠加层,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(
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();
},
);
}
}
Run Code Online (Sandbox Code Playgroud)
Dev*_*wal 65
这是(我认为)最直接的方法。
将以下内容复制粘贴到您的项目中。
更新: usingRenderProxyBox导致稍微更正确的实现,因为它会在每次重建子项及其后代时调用,而顶级 build() 方法并非总是如此。
注意:正如 Hixie在此处指出的那样,这并不是一种有效的方法。但它是最简单的。
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);
}
}
Run Code Online (Sandbox Code Playgroud)
然后,只需用 包裹您想要测量其大小的小部件MeasureSize。
var myChildSize = Size.zero;
Widget build(BuildContext context) {
return ...(
child: MeasureSize(
onChange: (size) {
setState(() {
myChildSize = size;
});
},
child: ...
),
);
}
Run Code Online (Sandbox Code Playgroud)
所以,是的,父的大小不能 可以,如果你足够努力取决于儿童的体形。
个人轶事 - 这对于限制小部件的大小非常方便,比如Align,它喜欢占用大量空间。
Bri*_*tty 11
我将此小部件制作为一个简单的无状态解决方案:
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,
);
}
}
Run Code Online (Sandbox Code Playgroud)
像这样使用它
ChildSizeNotifier(
builder: (context, size, child) {
// size is the size of the text
return Text(size.height > 50 ? 'big' : 'small');
},
)
Run Code Online (Sandbox Code Playgroud)
Yad*_*adu 10
让我给你一个小部件
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;
}
}
Run Code Online (Sandbox Code Playgroud)
编辑
只需将SizeProviderWidgetwith包裹起来OrientationBuilder以使其尊重设备的方向
如果您不想等待框架获取大小,但想在将其包含到树中之前了解它:
\n最简单的方法是遵循BuildOwner文档的示例。
\n通过以下你可以做
\nfinal size = MeasureUtil.measureWidget(MyWidgetTree());\nRun Code Online (Sandbox Code Playgroud)\nimport \'package:flutter/material.dart\';\nimport \'package:flutter/rendering.dart\';\n\n/// Small utility to measure a widget before actually putting it on screen.\n///\n/// This can be helpful e.g. for positioning context menus based on the size they will take up.\n///\n/// NOTE: Use sparingly, since this takes a complete layout and sizing pass for the subtree you\n/// want to measure.\n///\n/// Compare https://api.flutter.dev/flutter/widgets/BuildOwner-class.html\nclass MeasureUtil {\n static Size measureWidget(Widget widget, [BoxConstraints constraints = const BoxConstraints()]) {\n final PipelineOwner pipelineOwner = PipelineOwner();\n final _MeasurementView rootView = pipelineOwner.rootNode = _MeasurementView(constraints);\n final BuildOwner buildOwner = BuildOwner(focusManager: FocusManager());\n final RenderObjectToWidgetElement<RenderBox> element = RenderObjectToWidgetAdapter<RenderBox>(\n container: rootView,\n debugShortDescription: \'[root]\',\n child: widget,\n ).attachToRenderTree(buildOwner);\n try {\n rootView.scheduleInitialLayout();\n pipelineOwner.flushLayout();\n return rootView.size;\n } finally {\n // Clean up.\n element.update(RenderObjectToWidgetAdapter<RenderBox>(container: rootView));\n buildOwner.finalizeTree();\n }\n }\n}\n\nclass _MeasurementView extends RenderBox with RenderObjectWithChildMixin<RenderBox> {\n final BoxConstraints boxConstraints;\n _MeasurementView(this.boxConstraints);\n\n @override\n void performLayout() {\n assert(child != null);\n child!.layout(boxConstraints, parentUsesSize: true);\n size = child!.size;\n }\n\n @override\n void debugAssertDoesMeetConstraints() => true;\n}\nRun Code Online (Sandbox Code Playgroud)\n这将创建一个与主渲染树分开的全新渲染树,并且不会在屏幕上显示\xe2\x80\x99。
\n例如
\n print(\n MeasureUtil.measureWidget(\n Directionality(\n textDirection: TextDirection.ltr,\n child: Row(\n mainAxisSize: MainAxisSize.min,\n children: const [\n Icon(Icons.abc),\n SizedBox(\n width: 100,\n ),\n Text("Moin Meister")\n ],\n ),\n ),\n ),\n );\nRun Code Online (Sandbox Code Playgroud)\n会给你Size(210.0, 24.0)。
这是关于如何使用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),
),
);
}
}
Run Code Online (Sandbox Code Playgroud)
演示
日志
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
Run Code Online (Sandbox Code Playgroud)
更新:您也可以使用MediaQuery来实现类似的功能。
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
Run Code Online (Sandbox Code Playgroud)
findRenderObject()返回RenderBox用于给出绘制控件的大小的 ,它应该在构建控件树之后调用,因此它必须与一些回调机制或addPostFrameCallback()回调一起使用。
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,
),
],
),
);
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
| 归档时间: |
|
| 查看次数: |
22084 次 |
| 最近记录: |