弹性布局 Flex-Expanded和Flexible

什么是弹性布局

在软件开发和 UI 布局中,**弹性布局(Flexible Layout / Flex Layout)**指的是:子元素可以根据可用空间动态伸缩,占用父容器剩余空间或按比例分配空间,而不是固定尺寸。

换句话说,弹性布局的核心就是 空间可以“弹性”分配,不必每个子元素都写死宽高。

在 Flutter 中,弹性布局(Flex) 是一种 按比例分配空间的线性布局,可以理解为 “增强版的 Row/Column”,它允许子 Widget 按比例(flex 值)来分配父容器的剩余空间。

RowColumn 本质上就是 Flex 的封装,Flex 的方向不同而已。

Flex 的定义

和前面的RowColumn 类似。

Flex({
  Key? key,
  required Axis direction,      // 主轴方向: Axis.horizontal / Axis.vertical
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
  MainAxisSize mainAxisSize = MainAxisSize.max,
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
  TextDirection? textDirection,
  VerticalDirection verticalDirection = VerticalDirection.down,
  TextBaseline? textBaseline,
  List<Widget> children = const <Widget>[],
})
属性 说明
direction 主轴方向:水平(Row)或垂直(Column)
mainAxisAlignment 主轴对齐方式
crossAxisAlignment 交叉轴对齐方式
children 子 Widget 列表
mainAxisSize 主轴占用空间大小(min/max)

Expanded

Expanded 只能放在 Flex、Column、Row 中使用

Expanded的作用:

  • 强制子 Widget 占满剩余空间
  • flex 决定占剩余空间的比例

强制子 Widget 占满剩余空间

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter Demo', home: const FlexPage());
  }
}

class FlexPage extends StatelessWidget {
  const FlexPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          // 尽管设置了宽度,但由于使用了 Expanded,仍然会占据剩余空间
          Expanded(child: Container(color: Colors.amber, width: 100)),
          Container(color: Colors.blue, width: 100, height: 100),
        ],
      ),
    );
  }
}
class FlexPage extends StatelessWidget {
  const FlexPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          // 尽管设置了宽度,但由于使用了 Expanded,仍然会占据剩余空间
          Expanded(child: Container(color: Colors.amber, width: 10)),
          Expanded(child: Container(color: Colors.blue, width: 600)),
        ],
      ),
    );
  }
}

Expanded修饰的两个容器的width被平均分别,尽管两个容器都设置了width的大小

flex 决定占剩余空间的比例

class FlexPage extends StatelessWidget {
  const FlexPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          // 尽管设置了宽度,但由于使用了 Expanded,仍然会占据剩余空间
          Expanded(flex: 2, child: Container(color: Colors.amber, width: 10)),
          Expanded(flex: 1, child: Container(color: Colors.blue, width: 600)),
        ],
      ),
    );
  }
}

水平方向空间被 2:1 比例分配给黄色和蓝色容器

Flexible

子 Widget 可按比例占用空间,但允许自身缩小或包裹内容

fit 可选择 FlexFit.tight(填满)或 FlexFit.loose(包裹内容)

没有Flexible按照设置的widthheight布局

class FlexPage extends StatelessWidget {
  const FlexPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.grey,
        height: 500,
        child: Column(
          children: [
            Container(color: Colors.amber, height: 100),
            Container(color: Colors.blue, height: 100),
          ],
        ),
      ),
    );
  }
}

使用Flexible:

class FlexPage extends StatelessWidget {
  const FlexPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.grey,
        height: 500,
        child: Column(
          children: [
            Flexible(child: Container(color: Colors.amber)),
            Flexible(child: Container(color: Colors.blue)),
          ],
        ),
      ),
    );
  }

使用Flexible之后,两个容器共同分享了剩余的300的空间,平均每一个是250的高度。

另外,这里我的两个我都没有设置height,这是因为,如果我Flexible 不会自动忽略子组件的固定高度height: 100,而是让子组件 在剩余空间内,按需要伸缩。如果给给 Container 明确写了 height: 100,所以它们不会去填满剩余的 300 高度。所以我在这里去掉了height设置。

fit配置

fit 在 Flutter 的弹性布局里其实就是控制 “给子组件的空间是紧的还是松的”

FlexFit.loose(默认值):

  • 父组件把空间分配好后,你可以决定自己的尺寸(如果设置了固定高度,就用固定高度;没设置就会尽量扩展)

FlexFit.tight

  • 父组件把分配到的剩余空间按比例分好,然后强制子组件填满这块地盘(如果设置了高度和宽度也是无效的)。
  • Expanded 就是 Flexible(flex: 1, fit: FlexFit.tight) 的简写。
class FlexPage extends StatelessWidget {
  const FlexPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.grey,
        height: 500,
        child: Column(
          children: [
            Flexible(
              fit: FlexFit.tight,
              child: Container(color: Colors.amber, height: 100),
            ),
            Flexible(
              fit: FlexFit.tight,
              child: Container(color: Colors.blue, height: 100),
            ),
          ],
        ),
      ),
    );
  }
}

使用fix分配空间

class FlexPage extends StatelessWidget {
  const FlexPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.grey,
        height: 500,
        child: Column(
          children: [
            // Container(color: Colors.amber),
            // Container(color: Colors.blue),
            // Container(color: Colors.amber, height: 100),
            // Container(color: Colors.blue, height: 100),
            // Flexible(child: Container(color: Colors.amber, height: 100)),
            // Flexible(child: Container(color: Colors.blue, height: 100)),
            Flexible(
              flex: 1,
              fit: FlexFit.tight,
              child: Container(color: Colors.amber, height: 100),
            ),
            Flexible(
              flex: 2, 
              fit: FlexFit.tight,
              child: Container(color: Colors.blue, height: 100),
            ),
          ],
        ),
      ),
    );
  }
}

500的空间按照1:2的比例分配

Spacer

Spacer 其实就是 一个专门用来“占空”并推开其它组件的工具,它本质上就是 Expanded 的一个简化版。

在 Flutter 的 RowColumn 中,Spacer 会:

  • 占据可用的剩余空间
  • 不能设置宽度/高度
  • 可通过 flex 参数控制占用比例

内部实现等同于:

Expanded(
  flex: x, // 默认 1
  child: SizedBox.shrink(), // 一个啥也没有的空盒子
)

Spacer 主要用来在弹性布局中调整组件之间的间距,而不需要手动加 Expanded + Container

class FlexPage extends StatelessWidget {
  const FlexPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.grey,
        height: 500,
        child: Column(
          children: [
            Flexible(
              flex: 3,
              fit: FlexFit.tight,
              child: Container(color: Colors.amber, height: 100),
            ),
            const Spacer(), // 默认flex=1
            Flexible(
              flex: 3,
              fit: FlexFit.tight,
              child: Container(color: Colors.blue, height: 100),
            ),
            const Spacer(flex: 2), // flex=2
          ],
        ),
      ),
    );
  }
}

×

喜欢就点赞,疼爱就打赏