使用Expanded布局时报错The following assertion was thrown during performLayout

在使用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 中的弹性布局主要通过FlexExpanded来配合实现。

Flex

Flex组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用RowColumn会方便一些,因为RowColumn都继承自Flex,参数基本相同,所以能使用Flex的地方基本上都可以使用RowColumnFlex本身功能是很强大的,它也可以和Expanded组件配合实现弹性布局。接下来我们只讨论Flex和弹性布局相关的属性(其它属性已经在介绍RowColumn时介绍过了)。

Flex({
  ...
  required this.direction, //弹性布局的方向, Row默认为水平方向,Column默认为垂直方向
  List<Widget> children = const <Widget>[],
})

Flex继承自MultiChildRenderObjectWidget,对应的RenderObjectRenderFlexRenderFlex中实现了其布局算法。

Expanded

Expanded 只能作为 Flex 的孩子(否则会报错),它可以按比例“扩伸”Flex子组件所占用的空间。因为 RowColumn 都继承自 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(),
    );
  }
}

小结

弹性布局比较简单,唯一需要注意的就是RowColumn以及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) { }                                                 
}                                      
  • 种类

也可以参考:Flutter Widgets 之 Expanded和Flexible_一叶飘舟的博客-CSDN博客 

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/125380268