Flutter 中 Flexible、Expanded、Spacer 的使用技巧详解 | Flutter Widgets

4,427 阅读4分钟

这是我参与更文挑战的第11天,活动详情查看: 更文挑战

前言

前一篇我们聊了 Row、Column、Flex 布局及其各个参数对应的效果,其中 mainAxisAlignment 可以设置子项间的布局方式,但是这里仅可以对整体子项进行设置,不能够对布局子项进行更精细化的设置,这一篇我们将聊聊对子类进行更加精细化的布局设置方法。

Flexible(弹性组件)

这里的 Flexible 和上篇聊的 Flex 看着有点像,实际上这两个的实现完全不一样,作用的组件也不一样,前者作用于整体子项的布局,后者作用于单个子项的布局。Flexible 的源码可以看看上篇,这里我们先看看 Flex 的源码也是很简单的。
image.png

  • flex 填充比例
  • fit 可用空间填充方式
  • child 嵌套的子 Widget

Flex 填充比例

// 横向布局(可以看上一篇)
Row(
  children: [
    Flexible(
      // 第一个占用 1/6
      flex: 1,
      child: getItem(1),
    ),
    Flexible(
      // 第 2 个占用 2/6
      flex: 2,
      child: getItem(2),
    ),
    Flexible(
      // 第 3 个占用 3/6
      flex: 3,
      child: getItem(3),
    ),
  ],
)

getItem

  /// 获取子项目(这里使用了位置参数)
  Widget getItem(int index, [double width = 60, double height = 60]) {
    return Container(
      // 宽高设置 60
      width: width,
      height: height,
      // 设置背景色
      color: Colors.orange.shade200,
      // 设置间隙
      margin: EdgeInsets.all(2),
      // 设置子项居中
      alignment: Alignment.center,
      // 设置子项
      child: Text('$index'),
    );
  }
}

看效果

image.png
What? 按照常识来讲这不符合预期啊,刚才上面看到 fit 参数默认是 FlexFit.loose 并且这里 getItem 子项的宽高设置成了 60 ,所以会出现上面的现象。

做些修改

这里我们将 getItem 改为 getItem(1, null, 60) 设置为不限制宽度,看看效果

Flexible(
  flex: 1,
  fit: FlexFit.loose,
  child: getItem(1, null, 60),
)

image.png
这下就符合我们的预期了,我们再来做一些调整, getItem 不变,仅把 fit 参数改为 FlexFit.tight 看看效果

Flexible(
  flex: 1,
  fit: FlexFit.tight,
  child: getItem(1),
)

image.png
这里也符合我们的预期,这是为什么呢?

因为 FlexFit.loose 允许子项与最大的可用空间(1/6)一样大,但是可以更小,我们开始设置了子项宽度是 60 ,所以就是 60 了,不设置子项宽度就是按照允许的最大空间来了。 因为 FlexFit.tight 被迫会将子类填充可用空间(1/6),所以设置后即使我们设置了子项宽度也没有作用

小结一下

  • flex 可用空间比例
  • fit 子项可用空间填充方式
    • FlexFit.loose 允许子项 <= 可用空间
    • FlexFit.tight 子项 == 可用空间

Expanded(展开的)

这个 Widget 也是非常常用的,有了前面的理解,现在我们看看他的实现,就秒懂什么意思了,一起来
image.png
看到他继承自 Flexible 你就差不多懂了吧,然后我们再看看他 fit 设置的 FlexFit.tight (子项 == 可用空间),现在来看看下面的代码,你说效果是什么?

Row(
  children: [
    Expanded(
      flex: 1,
      child: getItem(1),
    ),
    Expanded(
      flex: 2,
      child: getItem(2),
    ),
    Expanded(
      flex: 3,
      child: getItem(3),
    ),
  ],
)

image.png
对了,正如你所料,他和上面我们 Flexible 展示的最后一个示例效果是一样的。

应用场景

先看看下面简单的异常效果
image.png
实现代码如下

Row(
  children: [
    getItem(1),
    Text(
      'ZeroFlutter 聊 Expanded,ZeroFlutter 聊 ExpandedZeroFlutter 聊 ExpandedZeroFlutter 聊 ExpandedZeroFlutter 聊 ExpandedZeroFlutter 聊 Expanded')
  ],
)

一般我们都要考虑适配性,你想要的可能是这样的
image.png
实现代码如下,我们使用 Expanded 包裹 Text ,然后设置了最多 2 行,超过 ... 显示

Row(
  children: [
    getItem(1),
    // 使用 Expanded 包裹 Text
    Expanded(
      child: Text(
        'ZeroFlutter 聊 Expanded,ZeroFlutter 聊 ExpandedZeroFlutter 聊 ExpandedZeroFlutter 聊 ExpandedZeroFlutter 聊 ExpandedZeroFlutter 聊 Expanded',
        overflow: TextOverflow.ellipsis,
        maxLines: 2,
      ),
    )
  ],
)

Spacer(弹片)

这个 Widget 可能你是第一次听说,但是从此以后你可能就会经常用到他了,非常好用,比如说我们要做到如下效果,你会怎么做?
image.png

// 你是这样想的吗?
Row(
  children: [
    getItem(1),
    Expanded(child: SizedBox()),
    getItem(2),
    Expanded(
      child: SizedBox(),
      flex: 2,
    ),
    getItem(3)
  ],
)

根据前面学的,你是上面👆🏻这样想的吗?其实我们可以更简单点,像下面👇🏻这样

Row(
  children: [
    getItem(1),
    Spacer(),
    getItem(2),
    Spacer(flex: 2),
    getItem(3)
  ],
)

走我们看看实现

image.png
算上注释总共 66 行代码,其实如果官方不提供,我们如果经常用到有这个需求,也可以自己封装一个。
实现和我们想的一样,这里就是使用 Expanded 包裹了一个 SizedBox

这里我们看到继承自了 StatelessWidget 不可变的,Flutter 中封装 Widget 是否可变取决于本身,这里我们 flex 设置了之后就不会变了,如果要变也是外部发生变化致使 Spacer 变化,所以这点理解很重要。

小结一下

到这里我们把 Flexible、Expanded、Spacer 的使用都聊了一下,并且也理清楚了他们的关系如下,以后用起来就方便啦。

  • Flexible => Expanded => Spacer

最后深夜更文不易,如有帮助请点赞支持哦

源码仓库

基于 Flutter 🔥 最新版本

参考链接

关注专栏

  • 此文章已收录到下面👇 的专栏,可以直接关注
  • 更多文章继续阅读|系列文章持续更新

👏 欢迎点赞➕收藏➕关注,有任何问题随时在下面👇评论,我会第一时间回复哦