写点什么

flutter 系列之:UI layout 简介

作者:程序那些事
  • 2022 年 6 月 10 日
  • 本文字数:4577 字

    阅读完需:约 15 分钟

简介

对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做 layout,就是用来描述如何将组件进行摆放的一个约束。


在 flutter 中,基本上所有的对象都是 widget,对于 layout 来说也不例外。也就是说在 flutter 中 layout 也是用代码来完成的,这和其他的用配置文件来描述 layout 的语言有所不同。


你可以把 layout 看做是一种看不见的 widget,这些看不见的 widget 是用来作用在可见的 widget 对象上,给他们实施一些限制。

flutter 中 layout 的分类

flutter 中的 layout widget 有很多,他们大概可以分为三类,分别是只包含一个 child 的 layout widget,可以包含多个 child 的 layout widget 和可滑动的 Sliver widgets。


这三种 layout 也有很多种具体的实现,对于 Single-child layout widgets 来说,包含下面这些 widgets:


  • Align -- 用来对其包含在其中的组件进行对其操作。

  • AspectRatio -- 对其中的组件进行比例缩放。

  • Baseline -- 通过使用子组件的 baseline 来进行定位。

  • Center -- 自组件位于中间。

  • ConstrainedBox -- 类似于 IOS 中的 constrain,表示子组件的限制条件。

  • Container -- 一个常用的 widget,可以用来包含多个其他的 widget。

  • CustomSingleChildLayout -- 将其单个子项的布局推迟。

  • Expanded -- 将 Row, Column 或者 Flex 的 child 进行扩展。

  • FittedBox -- 根据 fit 来缩放和定位其 child。

  • FractionallySizedBox -- 将 child 按照总可用空间进行调整。

  • IntrinsicHeight -- 一个将其 child 调整为 child 固有高度的小部件。

  • IntrinsicWidth -- 一个将其 child 调整为 child 固有宽度的小部件。

  • LimitedBox -- 限制一个 box 的 size。

  • Offstage -- 将 child 放入 render tree 中,但是却并不触发任何重绘。

  • OverflowBox -- 允许 child 覆盖父组件的限制。

  • Padding -- 为 child 提供 padding。

  • SizedBox -- 给定 size 的 box。

  • SizedOverflowBox -- 可以覆盖父组件限制的 box。

  • Transform -- 子组件可以变换。


以上是包含单个 child 的 layout 组件,下面是可以包含多个 child 的 layout 组件:


  • Column -- 表示一列 child。

  • CustomMultiChildLayout -- 使用代理来定位和缩放子组件。

  • Flow -- 流式布局。

  • GridView -- 网格布局。

  • IndexedStack -- 从一系列的 child 中展示其中的一个 child。

  • LayoutBuilder -- 可以依赖父组件大小的 widget tree。

  • ListBody -- 根据给定的 axis 来布局 child。

  • ListView -- 可滚动的列表。

  • Row -- 表示一行 child。

  • Stack -- 栈式布局的组件。

  • Table -- 表格形式的组件。

  • Wrap -- 可以对子 child 进行动态调整的 widget。


可滑动的 Sliver widgets 有下面几种:


  • CupertinoSliverNavigationBar -- 是一种 IOS 风格的导航 bar。

  • CustomScrollView -- 可以自定义 scroll 效果的 ScrollView。

  • SliverAppBar -- material 风格的 app bar,其中包含了 CustomScrollView。

  • SliverChildBuilderDelegate -- 使用 builder callback 为 slivers 提供 child 的委托。

  • SliverChildListDelegate -- 使用 list 来为 livers 提供 child 的委托。

  • SliverFixedExtentList -- 固定 axis extent 的 sliver。

  • SliverGrid -- child 是二维分布的 sliver。

  • SliverList -- child 是线性布局的 sliver。

  • SliverPadding -- 提供 padding 的 sliver。

  • SliverPersistentHeader -- 可变 size 的 sliver。

  • SliverToBoxAdapter -- 包含单个 box widget 的 Sliver。

常用 layout 举例

上面我们列出了所有的 flutter layout,他们几乎满足了我们在程序中会用到的所有 layout 需求,这里我们以两个最基本和最常用的 layout:Row 和 Column 为例,来详细讲解 layout 的使用。


Row 和 Column 都属于上面讲到的多个 child 的 layout widget,它里面可以包含多个其他的 widget 组件。


先看一下 Row 和 column 的定义。


class Row extends Flex {  Row({    Key? key,    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,    MainAxisSize mainAxisSize = MainAxisSize.max,    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,    TextDirection? textDirection,    VerticalDirection verticalDirection = VerticalDirection.down,    TextBaseline? textBaseline, // NO DEFAULT: we don't know what the text's baseline should be    List<Widget> children = const <Widget>[],  }) : super(    children: children,    key: key,    direction: Axis.horizontal,    mainAxisAlignment: mainAxisAlignment,    mainAxisSize: mainAxisSize,    crossAxisAlignment: crossAxisAlignment,    textDirection: textDirection,    verticalDirection: verticalDirection,    textBaseline: textBaseline,  );}
复制代码


class Column extends Flex {  Column({    Key? key,    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>[],  }) : super(    children: children,    key: key,    direction: Axis.vertical,    mainAxisAlignment: mainAxisAlignment,    mainAxisSize: mainAxisSize,    crossAxisAlignment: crossAxisAlignment,    textDirection: textDirection,    verticalDirection: verticalDirection,    textBaseline: textBaseline,  );}
复制代码


可以看到 Row 和 Column 都继承自 Flex,并且他们的构造方法都是调用了 Flex 的构造方法,两者的区别就在于 direction 不同,Row 的 direction 是 Axis.horizontal,而 Column 的 direction 是 Axis.vertical。


那么什么是 Flex 呢?


Flex 是一个 widget,在 Flex 中的子组件会按照某一个指定的方向进行展示。这个方向是可以控制的,比如横向或者竖向,如果你已经提前知道了主轴的方向,那么可以使用 Row 或者 Column 来替代 Flex,因为这样更加简洁。


在 Flex 中,如果想要 child 在某个方向填满可用空间,则可以将该 child 包装在 Expanded 中。


要注意的是,Flex 是不可滚动的,如果 Flex 中的 child 太多,超出了 Flex 中的可用空间,那么 Flex 将会报错,所以如果你需要展示很多 child 的情况下,可以考虑使用可滚动的组件,比如 ListView。


如果你只有一个 child,那么就没有必要使用 Flex 或者 Row 和 Column 了,可以考虑使用 Align 或者 Center 来对 child 进行定位。


在 Flex 中有几个非常重要的参数,比如 mainAxisAlignment 表示的是子组件沿主轴方向的排列规则,mainAxisSize 表示的是主轴的 size 大小,crossAxisAlignment 表示的是和主轴垂直轴的子组件排列规则。当然还有它最最重要的 children 属性,children 是一个 Widget 的 list 列表,用来存储要展示的子组件。


以 Row 为例,我们创建一个简单的 RowWidget:


class RowWidget extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Row(      textDirection: TextDirection.ltr,      children: [        YellowBox(),        YellowBox(),        YellowBox(),      ],    );  }}
复制代码


这里我们返回了一个 Row 对象,设置了 textDirection 和 children 属性。


children 里面是自定义的 YellowBox:


class YellowBox extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Container(      width: 50,      height: 50,      decoration: BoxDecoration(        color: Colors.yellow,        border: Border.all(),      ),    );  }}
复制代码


YellowBox 是一个长和宽都是 50 的正方形。我们这里使用了 BoxDecoration 对其上色。


最后将 RowWidget 放到 Scaffold 的 body 里面,如下所示:


  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text(widget.title),      ),      body: RowWidget()    );  }
复制代码


我们可以看到下面的图像:



大家可以看到 YellowBox 是紧贴在一起的,如果我们想要均匀分别该如何做呢?


我们可以在 Row 中添加一个属性叫做 mainAxisAlignment,取值如下:


mainAxisAlignment: MainAxisAlignment.spaceEvenly
复制代码


重新运行,生成的图像如下:



上面我们还提到了一个 Expanded 组件,可以用来填充剩余的可用空间,我们把最后一个 YellowBox 用 Expanded 围起来,如下所示:


    return Row(      textDirection: TextDirection.ltr,      mainAxisAlignment: MainAxisAlignment.spaceEvenly,      children: [        YellowBox(),        YellowBox(),        Expanded(          child: YellowBox(),        )      ],    );
复制代码


生成的图像如下:



可以看到最后一个 Box 填充到了整个 Row 剩余的空间。


大家要注意,这时候 mainAxisAlignment 是没有效果的。


如果观察 Expanded 的构造函数,可以看到 Expanded 还有一个 flex 属性:


  const Expanded({    Key? key,    int flex = 1,    required Widget child,  }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
复制代码


flex 属性表示的是 flex factor,默认值是 1,还是上面的例子,我们将 flex 调整为 2,看看效果:


children: [        YellowBox(),        YellowBox(),        Expanded(          flex:2,          child: YellowBox(),        )      ],
复制代码


运行的结果和 flex=1 是一样的,为什么呢?


事实上这个 flex 表示的是相对于其他 Expanded 的组件所占用的空间比例。我们可以讲所有的子组件都用 Expanded 进行扩充,然后再看看效果:


      children: [        Expanded(          child: YellowBox(),        ),        Expanded(          child: YellowBox(),        ),        Expanded(          flex: 2,          child: YellowBox(),        )      ],
复制代码


运行结果如下:



可以看到最后一个 child 占用的空间是前面两个的两倍。


如果我们想要在 YellowBox 中间添加空格怎么办呢?有两种方法。


第一种方法是使用 SizedBox,如下:


children: [        Expanded(          child: YellowBox(),        ),        SizedBox(          width: 100,        ),        Expanded(          child: YellowBox(),        ),        Expanded(          flex: 2,          child: YellowBox(),        )      ],
复制代码



SizedBox 里面可以包含子 child,从而重新设置子 child 的长度和高度。如果不包含子 child 则会生成一个空格。


还有一种方式是使用 Spacer,如下所示:


      children: [        Expanded(          child: YellowBox(),        ),        Spacer(flex: 2),        Expanded(          child: YellowBox(),        ),        Expanded(          flex: 2,          child: YellowBox(),        )      ],
复制代码


生成的图像如下:



Spacer 和 SizedBox 都可以生成空白,不同的是 Spacer 可以和 flex 一起使用,而 SizedBox 必须固定 size 大小。

总结

以上就是 fluter 中 layout 和的分类和基本 layout Row 和 Column 的使用情况了。


本文的例子:https://github.com/ddean2009/learn-flutter.git


更多内容请参考 http://www.flydean.com/07-flutter-ui-layout-overview/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 2022 年 06 月 10 日阅读数: 16
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
flutter系列之:UI layout简介_flutter_程序那些事_InfoQ写作社区