Flutter 自定义布局,根据子元素决定 widget 的大小
Posted
技术标签:
【中文标题】Flutter 自定义布局,根据子元素决定 widget 的大小【英文标题】:Flutter custom layout, decide widget's size based on its child 【发布时间】:2020-10-30 14:11:59 【问题描述】:understanding constraints 文章对于了解布局的工作原理非常有用。但是,我在创建一个按照那里解释的方式工作的自定义布局时遇到了问题。
文章说size是向上流动的,先获取children的size,然后根据children的size来决定自己的size。
似乎CustomSingleChildLayout
是使用一个孩子创建自定义布局的方法。在里面,根据我上面提到的文章,我想得到我孩子的尺码,然后据此决定我自己的尺码。然而,这似乎是不可能的。 getSize()
方法是使用约束调用的,因此我应该根据从上面收到的约束来决定我的小部件大小。我可以在getPositionForChild()
中获得孩子的尺寸,但这是在我在getSize()
中给出我的尺寸之后调用的。此时触发重新布局是有意义的,但似乎不允许我从布局系统调用的函数中执行此操作。
那么,问题是,实现自定义布局的最佳方式是什么,约束的布局行为向下流动,尺寸向上流动,如understanding constraints 中所述?
编辑:这里有一些代码来解释我真正想要的。
这就是我想要的外观。在这里,我手动提供了蓝色方块的宽度和高度。我不想那样做。我希望孩子的大小来确定正方形的大小。我希望大小从文本向上流动,蓝色方块的边长应为max(width, height)
。
https://codepen.io/gazialankus/pen/abdKyVR
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
class MyPage extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: SizedBox(
width: 180,
height: 180,
child: Container(
color: Colors.blue,
child: AspectRatio(
aspectRatio: 1,
child: Text(
"I want to be in a tight square.",
style: TextStyle(
backgroundColor: Colors.red
),
),
),
),
),
),
),
);
void main()
runApp(AppRoot());
有人建议我可以用Align
包裹它。我在这里尝试过,但Align
长大了。 Align
的 widthFactor
和 heightFactor
使用了孩子的尺寸,但不能这样使它成为正方形。去掉蓝色的Container
没有效果:
https://codepen.io/gazialankus/pen/MWKXvpO
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
class MyPage extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: Align(
alignment: Alignment.center,
child: Container(
color: Colors.blue,
child: Text(
"I want to be in a square.",
style: TextStyle(
backgroundColor: Colors.red
),
),
),
),
),
),
);
void main()
runApp(AppRoot());
这里我尝试用AspectRatio
做正方形,但它不关心孩子的大小,只是长大:
https://codepen.io/gazialankus/pen/KKVevXv
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
class MyPage extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: Align(
alignment: Alignment.center,
child: Container(
color: Colors.blue,
child: AspectRatio(
aspectRatio: 1,
child: Text(
"I want to be in a tight square.",
style: TextStyle(
backgroundColor: Colors.red
),
),
),
),
),
),
),
);
void main()
runApp(AppRoot());
在这里,我正在尝试进行自定义布局,但我无法使用孩子的尺寸来决定我的尺寸。在收到我孩子的之前,我必须决定我的尺码。一旦我知道我孩子的大小,试图强制重新布局会引发异常,该异常位于代码的末尾。 Codepen 静默失败。
https://codepen.io/gazialankus/pen/LYGrjxM
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
class MyPage extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: CustomSingleChildLayout(
delegate: MyDelegate(relayout: ValueNotifier<int>(0)),
child: Text(
"I want to be in a square.",
style: TextStyle(
backgroundColor: Colors.red
),
),
),
),
),
);
class MyDelegate extends SingleChildLayoutDelegate
ValueNotifier<int> relayout;
MyDelegate( this.relayout ) : super(relayout: relayout);
@override
bool shouldRelayout(SingleChildLayoutDelegate oldDelegate)
return desiredWidth == 100;
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints)
print('getConstraintsForChild');
return super.getConstraintsForChild(constraints);
double desiredWidth = 300;
@override
Offset getPositionForChild(Size size, Size childSize)
print('getPositionForChild');
print(size);
print(childSize);
if (size.width > childSize.width)
desiredWidth = childSize.width;
relayout.value++;
// ^trying to force a relayout
// throws exception, output is given at the bottom in comments
print("RELAYOUT");
return super.getPositionForChild(size, childSize);
@override
Size getSize(BoxConstraints constraints)
print('getSize');
return Size(desiredWidth, 100);
void main()
runApp(AppRoot());
/*
Performing hot reload...
Syncing files to device LG H990...
I/flutter (19850): getSize
I/flutter (19850): getConstraintsForChild
I/flutter (19850): getPositionForChild
I/flutter (19850): Size(300.0, 100.0)
I/flutter (19850): Size(148.0, 16.0)
════════ Exception caught by foundation library ════════════════════════════════════════════════════
The following assertion was thrown while dispatching notifications for ValueNotifier<int>:
'package:flutter/src/rendering/object.dart': Failed assertion: line 1527 pos 12: '_debugCanPerformMutations': is not true.
Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=BUG.md
When the exception was thrown, this was the stack:
#2 RenderObject.markNeedsLayout (package:flutter/src/rendering/object.dart:1527:12)
#3 RenderBox.markNeedsLayout (package:flutter/src/rendering/box.dart:2053:11)
#4 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:207:21)
#5 ValueNotifier.value= (package:flutter/src/foundation/change_notifier.dart:274:5)
#6 MyDelegate.getPositionForChild (package:m_health/ui/app_root.dart:64:16)
...
The ValueNotifier<int> sending notification was: ValueNotifier<int>#ac2fb(1)
════════════════════════════════════════════════════════════════════════════════════════════════════
Reloaded 2 of 522 libraries in 1,390ms.
I/flutter (19850): RELAYOUT
*/
【问题讨论】:
【参考方案1】:这应该可以解决您的问题。我们使用UnconstrainedBox
删除了传递下来的约束。我们只需要删除垂直,以便AspectRatio
使用宽度约束。然后我们要求子树按其固有大小来调整大小。当然,现在很容易溢出,因为它永远不会换行并且总是使用它的固有宽度。但是,您可以将UnconstrainedBox
包装在FittedBox
中,它永远不会溢出,而是缩放以适应可能小于其固有尺寸的父级。您可能还想尝试将UnconstrainedBox
上的轴参数设置为水平。然后 IntrinsicWidth 将被限制为父宽度。
import 'package:flutter/material.dart';
class AppRoot extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo', // Appears in app switcher
theme: ThemeData(
primaryColor: Colors.grey.shade300,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyPage(),
);
class MyPage extends StatelessWidget
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Container(
color: Colors.yellow,
child: UnconstrainedBox(
child: IntrinsicWidth(
child: Container(
color: Colors.blue,
child: AspectRatio(
aspectRatio: 1,
child: Text(
"I want to be in a tight square.",
style: TextStyle(backgroundColor: Colors.red),
),
),
),
),
),
),
),
);
void main()
runApp(AppRoot());
【讨论】:
不错!谢谢你。不错的UnconstrainedBox
和IntrinsicWidth
组合,绝对解决了我的问题。当我有时间时,我会尝试跟进我最初的任务,并想出一种方法来创建类似于“理解约束”文章的自定义小部件。以上是关于Flutter 自定义布局,根据子元素决定 widget 的大小的主要内容,如果未能解决你的问题,请参考以下文章
Flutter自定义MultiChildRenderObjectWidget实现圆环布局效果
Flutter自定义MultiChildRenderObjectWidget实现圆环布局效果
Flutter学习笔记(22)--单个子元素的布局Widget(ContainerPaddingCenterAlignFittedBoxOffstageLimitedBoxOverflo