在使用Row和Column布局,在用了Expanded组件时报错如下:
I/flutter (14104): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter (14104): The following assertion was thrown during performLayout():
I/flutter (14104): RenderFlex children have non-zero flex but incoming width constraints are unbounded.
I/flutter (14104): When a row is in a parent that does not provide a finite width constraint, for example if it is in a
I/flutter (14104): horizontal scrollable, it will try to shrink-wrap its children along the horizontal axis. Setting a
I/flutter (14104): flex on a child (e.g. using Expanded) indicates that the child is to expand to fill the remaining
I/flutter (14104): space in the horizontal direction.
I/flutter (14104): These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child
I/flutter (14104): cannot simultaneously expand to fit its parent.
I/flutter (14104): Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible
I/flutter (14104): children (using Flexible rather than Expanded). This will allow the flexible children to size
I/flutter (14104): themselves to less than the infinite remaining space they would otherwise be forced to take, and
I/flutter (14104): then will cause the RenderFlex to shrink-wrap the children rather than expanding to fit the maximum
I/flutter (14104): constraints provided by the parent.
I/flutter (14104): If this message did not help you determine the problem, consider using debugDumpRenderTree():
I/flutter (14104): https://flutter.dev/debugging/#rendering-layer
I/flutter (14104): http://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html
I/flutter (14104): The affected RenderFlex is:
I/flutter (14104): RenderFlex#ad712 relayoutBoundary=up14 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE(creator: Row ← Column ← Row ← Padding ← Semantics ← DefaultTextStyle ← AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#e6eab ink renderer] ← NotificationListener<LayoutChangedNotification> ← CustomPaint ← _ShapeBorderPaint ← PhysicalShape ← ⋯, parentData: offset=Offset(0.0, 0.0); flex=null; fit=null (can use size), constraints: BoxConstraints(unconstrained), size:
MISSING, direction: horizontal, mainAxisAlignment: start, mainAxisSize: max, crossAxisAlignment:
center, textDirection: ltr, verticalDirection: down)
I/flutter (14104): The creator information is set to:
I/flutter (14104): Row ← Column ← Row ← Padding ← Semantics ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
I/flutter (14104): _InkFeatures-[GlobalKey#e6eab ink renderer] ← NotificationListener<LayoutChangedNotification> ←
I/flutter (14104): CustomPaint ← _ShapeBorderPaint ← PhysicalShape ← ⋯
I/flutter (14104): The nearest ancestor providing an unbounded width constraint is: RenderFlex#63b6e relayoutBoundary=up12 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE:
I/flutter (14104): creator: Row ← Padding ← Semantics ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
I/flutter (14104): _InkFeatures-[GlobalKey#e6eab ink renderer] ← NotificationListener<LayoutChangedNotification> ←
I/flutter (14104): CustomPaint ← _ShapeBorderPaint ← PhysicalShape ← _MaterialInterior ← Material ← ⋯
I/flutter (14104): parentData: offset=Offset(0.0, 0.0) (can use size)
I/flutter (14104): constraints: BoxConstraints(w=361.4, 0.0<=h<=Infinity)
I/flutter (14104): size: MISSING
I/flutter (14104): direction: horizontal
I/flutter (14104): mainAxisAlignment: start
I/flutter (14104): mainAxisSize: max
I/flutter (14104): crossAxisAlignment: start
I/flutter (14104): textDirection: ltr
I/flutter (14104): verticalDirection: down
I/flutter (14104): See also: https://flutter.dev/layout/
I/flutter (14104): If none of the above helps enough to fix this problem, please don't hesitate to file a bug:
I/flutter (14104): https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter (14104):
I/flutter (14104): Widget creation tracking is currently disabled. Enabling it enables improved error messages. It can
在报错时找到有效线索信息不多,因为结构创建和目前抛出来的错误还是有些差异,
但其中有一句是如下:
大小太过于灵活,当时我的代码是Row()组件内还套了Row()组件的结构,
在报错时找到有效线索信息不多,因为结构创建和目前抛出来的错误还是有些差异,
但其中有一句是如下:
大小太过于灵活,当时我的代码是Row()组件内还套了Row()组件的结构
从上方线索查找问题,这时候我就想到,我当时是直接在底层Row()组件中直接利用撑开组件Expanded,而上一层父组件或某个N父组件大小是特别灵活没有定大小是弹性的。
所以整体在flutter中无法计算大小来撑开布局,这时候我就把父组件上面的Row内也使用了Expanded组件撑开才得以不报错
PS:这里说明一下,你父层组件你是可以使用 外层包裹一个固定宽度组件方式来实现不报错,但局限了整体弹性布局业务考虑了!
以下是出问题的代码
class _SearchListState extends State<SearchList> {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, idx) {
return Container(
child: Card(
margin: EdgeInsets.only(bottom: 10), // 外边距
elevation: 3, // 阴影大小
child: Padding(
padding: EdgeInsets.fromLTRB(5, 15, 5, 15),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 此处没有进行Expanded组件撑开,所以里面嵌套的Row使用撑开组件会报错误
cardRigthWidget(),
],
),
),
),
);
},
);
}
Widget cardRigthWidget() {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: RichText(
text: TextSpan(
text: '标题',
style: TextStyle(
color: Theme.of(context).primaryColor), // 设置默认文字样式
children: <TextSpan>[
TextSpan(text: 'title_mo_blockhain'),
],
),
),
),
],
),
],
);
}
}
最后修复的代码,就是在最顶层Row组件内在加上一个Expanded撑开组件即可,这样flutter里面才可以计算组件盒子大小
class _SearchListState extends State<SearchList> {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, idx) {
return Container(
child: Card(
margin: EdgeInsets.only(bottom: 10), // 外边距
elevation: 3, // 阴影大小
child: Padding(
padding: EdgeInsets.fromLTRB(5, 15, 5, 15),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 改造一下,父组件包裹Expanded撑开组件
Expanded(
child: cardRigthWidget(),
),
],
),
),
),
);
},
);
}
// 。。。。其它代码还是之前的
}
此时报错就顺序解决,渲染正常
总结:像flutter内不能像HTML一样写完全的flex弹性使用,多多少少还是有些坑点的,比如这次Row弹性组件内在套用了其它的Row,顶层必须先撑开计算,这样嵌套层的Row才能正常使用Expanded组件。
否则引起flutter计算组件不正确,直接抛出父组件一直是弹性无法正常计算宽度撑开。
弹性布局(Flex)
弹性布局允许子组件按照一定比例来分配父容器空间。弹性布局的概念在其它UI系统中也都存在,如 H5 中的弹性盒子布局,Android中 的FlexboxLayout
等。Flutter 中的弹性布局主要通过Flex
和Expanded
来配合实现。
Flex
Flex
组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用Row
或Column
会方便一些,因为Row
和Column
都继承自Flex
,参数基本相同,所以能使用Flex的地方基本上都可以使用Row
或Column
。Flex
本身功能是很强大的,它也可以和Expanded
组件配合实现弹性布局。接下来我们只讨论Flex
和弹性布局相关的属性(其它属性已经在介绍Row
和Column
时介绍过了)。
Flex({
...
required this.direction, //弹性布局的方向, Row默认为水平方向,Column默认为垂直方向
List<Widget> children = const <Widget>[],
})
Flex
继承自MultiChildRenderObjectWidget
,对应的RenderObject
为RenderFlex
,RenderFlex
中实现了其布局算法。
Expanded
Expanded 只能作为 Flex 的孩子(否则会报错),它可以按比例“扩伸”Flex
子组件所占用的空间。因为 Row
和Column
都继承自 Flex,所以 Expanded 也可以作为它们的孩子。
const Expanded({
int flex = 1,
required Widget child,
})
flex
参数为弹性系数,如果为 0 或null
,则child
是没有弹性的,即不会被扩伸占用的空间。如果大于0,所有的Expanded
按照其 flex 的比例来分割主轴的全部空闲空间。下面我们看一个例子:
class FlexLayoutTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
//Flex的两个子widget按1:2来占据水平空间
Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
flex: 1,
child: Container(
height: 30.0,
color: Colors.red,
),
),
Expanded(
flex: 2,
child: Container(
height: 30.0,
color: Colors.green,
),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: SizedBox(
height: 100.0,
//Flex的三个子widget,在垂直方向按2:1:1来占用100像素的空间
child: Flex(
direction: Axis.vertical,
children: <Widget>[
Expanded(
flex: 2,
child: Container(
height: 30.0,
color: Colors.red,
),
),
Spacer(
flex: 1,
),
Expanded(
flex: 1,
child: Container(
height: 30.0,
color: Colors.green,
),
),
],
),
),
),
],
);
}
}
运行效果如图所示:
示例中的Spacer
的功能是占用指定比例的空间,实际上它只是Expanded
的一个包装类,Spacer
的源码如下:
class Spacer extends StatelessWidget {
const Spacer({Key? key, this.flex = 1})
: assert(flex != null),
assert(flex > 0),
super(key: key);
final int flex;
@override
Widget build(BuildContext context) {
return Expanded(
flex: flex,
child: const SizedBox.shrink(),
);
}
}
小结
弹性布局比较简单,唯一需要注意的就是Row
、Column
以及Flex
的关系。
Expanded组件可以使Row、Column、Flex等子组件在其主轴方向上展开并填充可用空间(例如,Row在水平方向,Column在垂直方向)。如果多个子组件展开,可用空间会被其flex factor(表示扩展的速度、比例)分割。
Expanded组件必须用在Row、Column、Flex内,并且从Expanded到封装它的Row、Column、Flex的路径必须只包括StatelessWidgets或StatefulWidgets组件(不能是其他类型的组件,像RenderObjectWidget,它是渲染对象,不再改变尺寸了,因此Expanded不能放进RenderObjectWidget)。
RenderObjectWidget
RenderObjectWidget为RenderObjectElement提供配置信息。
RenderObjectElement包装了RenderObject,RenderObject为应用程序提供真正的渲染。
RenderObjectWidget是个抽象类。
abstract class RenderObjectWidget extends Widget {
const RenderObjectWidget({ Key key }) : super(key: key);
/// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass.
@override
RenderObjectElement createElement();
/// 使用`RenderObjectWidget`信息,
///创建一个`RenderObjectWidget`表示的`RenderObject`实例。
///创建时机:
///`[RenderObjectElement.mount]`方法中使用`RenderObjectElement`创建。
///挂载时,调用关联的此`widget`创建其对应的`RenderObject`
@protected
RenderObject createRenderObject(BuildContext context);
/// 复制此[RenderObjectWidget]描述的配置到给定的[RenderObject],
///此`RenderObject`类型将与此`RenderObjectWidget`的
///[createRenderObject]返回的`RenderObject`类型相同。
/// 调用时机:[RenderObjectElement.update]
@protected
void updateRenderObject(BuildContext context, covariants RenderObject renderObject) { }
///此`widget`前一个关联的`RenderObject`已经从树中移除。
///此处的`RenderObject`是其的一个副本。
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
-
种类