Flutter报A RenderFlex overflowed错误(宽高度溢出)或者Flutter Widget不显示的解决办法(Expanded、Flexible)

我们在用Flutter写ui的时候,经常会遇见写完Widget后不显示,

例如如下代码:
代码很简单,实际上就是一个行控件里面放了一个Text和一个ListView。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter"),
      ),
      body: Column(
        children: <Widget>[
          _titleWidget(),
          _listView(),
        ],
      ),
    );
  }
}

_titleWidget() {
  return Container(
    color: Colors.grey,
    alignment: Alignment.center,
    child: Text("标题"),
    height: 50,
  );
}

_listView() {
  var datas = List.generate(100, (index) {
    return "item ${index + 1}";
  });

  return ListView.builder(
    itemBuilder: (BuildContext context, int index) {
      return Container(
        child: Text("${datas[index]}"),
        height: 40,
        alignment: Alignment.center,
        decoration: BoxDecoration(
            border: Border(bottom: BorderSide(color: Colors.grey))),
      );
    },
    itemCount: datas.length,
  );
}

运行后效果图如下:
在这里插入图片描述

发现ListView没有显示出来,然后控制台给出错误提示如下

I/flutter (22718): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter (22718): The following assertion was thrown during performResize():
I/flutter (22718): Vertical viewport was given unbounded height.
I/flutter (22718): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter (22718): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter (22718): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter (22718): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter (22718): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter (22718): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
I/flutter (22718): the height of the viewport to the sum of the heights of its children.

通常情况下是因为我们在Row或者是Column这种没有确定宽高度的Widget中使用了同样没有确定宽高度的子Widget导致的,例如ListViewWrapGridView等,Flutter不知道该用什么样的宽高来渲染widget,所以就会显示不出来。

解决办法如下:
1. 给定宽度或者高度
一般根据滚动方向来决定,本例中ListView是垂直滚动的,所以设置下高度就可以了。

如:

_listView() {
  var datas = List.generate(100, (index) {
    return "item ${index + 1}";
  });

  return Container(
    height: 300,
    child: ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Container(
          child: Text("${datas[index]}"),
          height: 40,
          alignment: Alignment.center,
          decoration: BoxDecoration(
              border: Border(bottom: BorderSide(color: Colors.grey))),
        );
      },
      itemCount: datas.length,
    ),
  );
}

这里我们给ListView外面套了一层Container,并指定其高度为300,运行效果如下:

在这里插入图片描述
可以看到,此时ListView已经显示出来了,但是,通常情况下,我们希望ListView占满剩下的所有高度,由于每个设备的屏幕高度肯定是有不一样的,所以,把高度写死肯定是不合适的。

2.使用ListView子控件的总高度来设置ListView的高度

在之前的Flutter 列表控件ListView 这篇博客中我们知道,shrinkWrap属性可以控制是否根据子widget的总长度来设置ListView的长度。
那这里我们既然不能把ListView的高度写死,那我们使用子widget的总长度来设置ListView的高度不就可以了吗?
下面我们来试一下。
代码如下:

_listView() {
  var datas = List.generate(100, (index) {
    return "item ${index + 1}";
  });

  return ListView.builder(
     shrinkWrap: true,//使用子控件的总长度来设置ListView的长度(这里的长度为高度)
    itemBuilder: (BuildContext context, int index) {
      return Container(
        child: Text("${datas[index]}"),
        height: 40,
        alignment: Alignment.center,
        decoration: BoxDecoration(
            border: Border(bottom: BorderSide(color: Colors.grey))),
      );
    },
    itemCount: datas.length,
  );
}

我们将shrinkWrap设置为true,再来看看运行效果。

在这里插入图片描述

运行后发现底部有黄色的区域,然后控制台报如下错误:

 ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
The following message was thrown during layout:
A RenderFlex overflowed by 3410 pixels on the bottom. 
The overflowing RenderFlex has an orientation of Axis.vertical.
 The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and
 black striped pattern. This is usually caused by the contents being too big for the RenderFlex.
 Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the
RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be
seen. If the content is legitimately bigger than the available space, consider clipping it with a
ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex,
like a ListView.

告诉我们底部溢出了,原因是屏幕没有这么多的高度来显示ListView,显然,shrinkWrap设置为true也是不好使的。

如果在Android原生中来处理这种布局的话,很简单,我们使用LinearLayout布局,Title给定高度,ListView权重为1,即ListView高度占满剩余高度即可。

那Flutter中有没有类似的布局呢?
我们去Flutter控件目录 中找找看。
发现了Expanded
在这里插入图片描述

Expanded是一个用展开Row、Column、或者Flex子控件的一个Widget,用于填充可用空间。这个好像可以满足我们的需求,下面我们详细看看它。

Flutter Expanded

源码如下:

class Expanded extends Flexible {
  /// Creates a widget that expands a child of a [Row], [Column], or [Flex]
  /// expand to fill the available space in the main axis.
  const Expanded({
    Key key,
    int flex = 1,
    @required Widget child,
  }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}

可以看到,Expanded继承自Flexible ,那我们直接看Flexible好了。

Flutter Flexible

Flexible源码如下:

  const Flexible({
    Key key,
    this.flex = 1,
    this.fit = FlexFit.loose,
    @required Widget child,
  }) : super(key: key, child: child);

可以看到,其构造方法很简单,FlexibleExpanded只多了一个fit 属性

其中
flex:用于控制自身在主轴上占据的空间比,默认值为1,如果值为0,则表示子控件自己决定其大小,一般不会这么用。
fit:用于控制如何将子控件放到可用空间中,有两个值,我反正是没看懂两个值的区别是什么,所以就不介绍了,还请指导的大佬指点一下,感激不尽。

实际上Flutter中的FlexibleExpanded中的flex就跟Android原生中的LinearLayout中的weight(权重)概念差不多了。

那下面我们来把上面的代码改一下:

_listView1() {
  var datas = List.generate(100, (index) {
    return "item ${index + 1}";
  });

  return Expanded(
    child: ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Container(
          child: Text("${datas[index]}"),
          height: 40,
          alignment: Alignment.center,
          decoration: BoxDecoration(
              border: Border(bottom: BorderSide(color: Colors.grey))),
        );
      },
      itemCount: datas.length,
    ),
  );
}

运行效果如下:
在这里插入图片描述

基本上就达到我的要求了。
下面我们再写一个ListView,并让他的flex值为2。

_listView2() {
  var datas = List.generate(100, (index) {
    return "item ${index + 1}";
  });

  return Flexible(
    flex: 2,
    child: ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Container(
          child: Text("${datas[index]}"),
          height: 40,
          alignment: Alignment.center,
          decoration: BoxDecoration(
              border: Border(bottom: BorderSide(color: Colors.grey))),
        );
      },
      itemCount: datas.length,
    ),
  );
}

下面是全部代码:
正常情况下ListView1的高度为主轴剩余空间的三分之一,ListView2高度为主轴剩余空间的三分之二,为了方便观看,我在两个列表之间加上了一个红色的分割线。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter"),
      ),
      body: Column(
        children: <Widget>[
          _titleWidget(),
          _listView1(),
          Container(
            decoration: BoxDecoration(
              border: Border(
                bottom: BorderSide(
                  color: Colors.red,
                ),
              ),
            ),
          ), //红色分割线
          _listView2(),
        ],
      ),
    );
  }
}

_titleWidget() {
  return Container(
    color: Colors.grey,
    alignment: Alignment.center,
    child: Text("标题"),
    height: 50,
  );
}

_listView1() {
  var datas = List.generate(100, (index) {
    return "item ${index + 1}";
  });

  return Expanded(
    child: ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Container(
          child: Text("${datas[index]}"),
          height: 40,
          alignment: Alignment.center,
          decoration: BoxDecoration(
              border: Border(bottom: BorderSide(color: Colors.grey))),
        );
      },
      itemCount: datas.length,
    ),
  );
}

_listView2() {
  var datas = List.generate(100, (index) {
    return "item ${index + 1}";
  });

  return Flexible(
    flex: 2,
    child: ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Container(
          child: Text("${datas[index]}"),
          height: 40,
          alignment: Alignment.center,
          decoration: BoxDecoration(
              border: Border(bottom: BorderSide(color: Colors.grey))),
        );
      },
      itemCount: datas.length,
    ),
  );
}

运行效果:

在这里插入图片描述

可以看到,跟我们的预期一样。

实际上,我们可以简单的把Flutter中的ExpandedFlexible控件看作是Android原生的LinearLayout,他们的效果很相似。

好了,Flutter中的ExpandedFlexible 大概就是这些


如果你觉得本文对你有帮助,麻烦动动手指顶一下,算是对本文的一个认可。也可以关注我的 Flutter 博客专栏,我会不定期的更新,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XeonYu

码字不易,鼓励随意。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值