Flutter 中,除了能确定页面内容很少的情况,一般的页面都会使用 SingleChildScrollView 作为可滚动的容器,组件先放在 Column 中, Column 再作为 SingleChildScrollView 的 child 这样来处理。

但是如果在 Column 需要使用 Expanded ,那就直接报错了。

RenderFlex children have non-zero flex but incoming height constraints are unbounded.

错误内容大致是 弹性渲染的子组件需要 非0 的弹性值,但是子组件的高度没有边界。

这是因为 Expanded 本身是需要边界约束的,而 SingleChildScrollView 没有什么边界,所以就报错了。 那如果想在 SingleChildScrollView 的 child 的 Column 里想加上 Expanded ,应该怎么去做。

可以如下解决:

  • 使用 LayoutBuilder 获取窗口的高度,SingleChildScrollView 作为 LayoutBuilder 的 child 。
  • SingleChildScrollView 的 child 使用 ConstrainedBoxConstrainedBox 约束高度设置为 LayoutBuilder 的窗口高度。
  • ConstrainedBox 的 child 使用 IntrinsicHeight ,该组件可以将其子组件的宽度调整为其本身实际的高度。(网上说 IntrinsicHeight 非常消耗性能)
  • IntrinsicHeight 的 child 再设置为 Column, 后面再使用 ColumnExpaneded 的组合就可以了。
  • 上面的方法是 SingleChildScrollView 的滚动方向为垂直时的做法。
    滚动方向为水平时,上面步骤中的高度换成宽度,IntrinsicHeight 换成 IntrinsicWidth, 然后 Column 换成 Row 即可。

    文章开头处的图片,有背景色的部分都是使用的 Expaned
    外层的 Column child 的 Expaneded 里又放了一个 Column
    然后内层的 Column 里也使用了 Expanded
    如果没有 IntrinsicHeight 的约束,最外层不使用 SingleChildScrollView 也会崩掉。

    文章开头处的图片的完整代码:

    import 'package:flutter/material.dart';
    void main() {
      runApp(const MyApp());
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(
            primarySwatch: Colors.blue,
          home: const MyHomePage(),
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key});
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('SingleChildScrollView and Expanded')),
          body: LayoutBuilder(
            builder: (context, constraint) {
              return SingleChildScrollView(
                child: ConstrainedBox(
                  constraints: BoxConstraints(minHeight: constraint.maxHeight),
                  child: IntrinsicHeight(
                    child: Column(
                      children: <Widget>[
                        const Text("Header"),
                        TextFormField(
                          decoration: const InputDecoration(
                            label: Text('Item'),
                          controller: TextEditingController(text: 'value'),
                        Container(
                          height: 8.0,
                        Expanded(
                          child: Container(
                            color: Colors.greenAccent,
                            child: Column(
                              children: [
                                const Text('Inner Header'),
                                TextFormField(
                                  decoration: const InputDecoration(
                                    label: Text('Sub item'),
                                  controller: TextEditingController(text: 'sub value'),
                                Expanded(
                                  child: Container(
                                    constraints: const BoxConstraints.expand(),
                                    margin: const EdgeInsets.all(8.0),
                                    color: Colors.lightGreen,
                                    child: const Text("Inner Body"),
                                const Text("Inner Footer"),
                        const Text("Footer"),
    

    本文正在参加「金石计划」