Flutter 布局控件完结篇

本文对 Flutter 的 29 种布局控件进行了总结分类,讲解一些布局上的优化策略,以及面对具体的布局时,如何去选择控件。

1. 系列文章

前前后后也算是拖拖拉拉的写了一些 Flutter 的文章,写的也都比较粗略。最近工作调动,内部换了部门,一顿瞎忙活,也打乱了原本的分享计划。

从我最开始接触 Flutter 到现在,差不多四个多月了。在这段时间里面,Flutter 也发布了 Release Preview 版本。各个技术网站本着先拨头筹的心态,推广了几波,国内的人气跟着也起来了不少。全世界 Flutter 开发人员中,国内从业者占据了很大的比重,这个现象本身并不能说明什么,不过可以反映一点,有商业诉求吧。当然观望的还是占绝大部分,除了一些个人开发者爱折腾外,也就是一些大的业务成熟到不能再成熟的团队,内部消化人员去折腾这个了。

插个题外话,有感于最近的工作变动,这段时间胡思乱想的比较多。一门技术对程序员来说到底意味着什么?如果不需要再为生计奔波,是否还会对目前已上手的技术感兴趣?如果你现在的项目所需要的技术,对你个人而言毫无加成,只会浪费你的时间,让你在已有的技术栈上渐行渐远,你是否还会参与这个项目。只有极少数人会遇上逆天改命的项目,不管参与什么项目,技术人员的立身之本始终是技术(高管或者打算换行的除外),技术的选型,除去时间效率后续维护等普适性的考虑要素外,排在第一位的始终应该是对自身的提高,扯的有些远了哈。

1.2 本质

我数了一下我文章总结过的布局控件,总共有 29 种。乍看会觉得真鸡毛的多,不乍看,也会觉得鸡毛的真多。为什么其他的移动平台没有这么多布局控件呢?其实不然,其他平台没有这么细分。

以 Android 平台为例,最基础的几种布局例如 LinearLayout、RelativeLayout、ConstraintLayout 等等。很多 Flutter 的控件,对于 Android 来说,仅仅是一个属性的设置问题。

再往上看,iOS、Android、Web 这些平台的布局,其实最基本就那几种,线性布局、绝对布局、相对布局等等。Flutter 也逃不出这些,那为什么 Flutter 现在有这么多布局控件呢?

第一点,之前文章介绍过的,Flutter 的理念是万物皆为 widget。这和 Flutter 的实现机制有关,而不是因为它在布局上有什么特殊性,这也是最主要的一点。

第二点,我觉得是因为这是 Flutter 的初期,如果有经历过一个技术的完整发展周期,就会明白,前期只是提供各种零件,只有商业支撑或者人员支撑足够的时候,才会去优化零件。而现在就是这么一种资源不足的状态。各种组件可以合并的有很多,底层的实现机制不会变,只是再加一层即可,这也是可以造轮子的地方,例如封装一套适用于 Android、iOS 或者 Web 人员的控件库等。

第三点,跟初期相关,一套新的技术,各种东西不可能一下子全想明白,路总是走着走着才发现走歪了,就像一些控件,可能一些地方合适,但是一些新的地方又不太合适,所以就再造一个,所以有些控件看起来功能十分相似。

说了这么多,我其实就想说明一点,Flutter 现在还只是处在社会发展的初级阶段,还处在温饱问题都解决不了的状态,想达到小康还需要很长的一段路要走。

2. 单节点控件

单节点控件,顾名思义就是只有一个节点的布局控件。这种控件有多少个呢,我之前文章总结过的有 18 种,现阶段还是不排除增加的可能,哈哈。

2.1 分类

在这小节里,我尝试从多个维度去对这些控件进行分类,希望这样可以帮助大家理解。

2.1.1 按照继承划分

上面是这 18 种控件的父节点层面的继承关系,唯一不同的一个控件就是 Container。所以按照是否继承自 SingleChildRenderObjectWidget 的分类如下:

继承自 StatelessWidget 的控件,有 Container。

继承自 SingleChildRenderObjectWidget 的控件,有 Padding、Align、Center、FittedBox、AspectRatio、ConstrainedBox、Baseline、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth、LimitedBox、Offstage、OverflowBox、SizedBox、SizedOverflowBox、Transform、CustomSingleChildLayout。

Container 是一个组合控件,不是一个基础控件,这点从继承关系就可以看出来。

2.1.2 按照功能是否单一划分

分类如下:

功能不单一的控件,Container、Transform、FittedBox、SizedOverflowBox。

功能单一的控件,有 Padding、Align、Center、AspectRatio、ConstrainedBox、Baseline、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth、LimitedBox、Offstage、OverflowBox、SizedBox、CustomSingleChildLayout。

先在此处小结一下,可以看出 Container 的特殊之处了吧,为什么 Container 这么特殊了。这个特殊要从两个层面去看。

对于 Flutter 而言,Container 是特殊的,因为它不是功能单一的控件,是一个组合的控件,所以它相对于 Flutter 是特殊的。

对于移动端开发者而言,它不是特殊的,因为很多 UI 都是一些基础功能组合的,这样能让开发者更方便的使用。

那能得出什么结论呢?我个人觉得,Container 这种组合的控件会越来越多,也会有个人开发者去开发这种通用型的组合控件,这是一个大趋势,是 Flutter 走向易用的一小步。

2.1.3 按照功能划分

在此处我按照定位、尺寸、绘制三部分来尝试着去做功能的划分,当然这个划分并不绝对,仁者见仁吧。

定位控件:Container、Align、Center、FittedBox、Baseline、Transform。

尺寸控件:Container、FittedBox、AspectRatio、ConstrainedBox、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth、LimitedBox、SizedBox、SizedOverflowBox。

绘制控件:Container、Padding、Offstage、OverflowBox、SizedOverflowBox、Transform。

有一个控件并没有归到这三类中,CustomSingleChildLayout 可以自定义实现,此处不做分类。Baseline 可以把它放到绘制里面去,此处我按照调节文字的位置去做分类,这个大家知道就行,并不是说只能这么划分。

对于绘制控件,其实分的有些杂,我把显示相关的都归到这里,例如是否显示、内边距、是否超出显示以及变形等等。

每一种大类,Flutter 都提供了多种控件。经过这么划分,可以看出很多控件功能的交叉,很多时候一个属性的事情,Flutter 还是分出了一个控件。

2.2 使用

单节点控件虽然这么多,但是大部分不会挨个去尝试。对于大部分人而言,都是佛系的用法,一个控件能够使用,就一直用到死。

在布局上,大方向还是不停的拆,把一张设计图,拆成一棵树,每个节点根据需要,选择合适的控件,然后从根部开始不停嵌套,布局就完成了。

2.3 控件的选择

控件种类繁多,真正使用的时候该如何去选择呢?有万金油的做法,不管啥都用 Container,这也是很多初接触的人经常干的方式。这么做的确可以按照设计图把布局给实现了,但是会涉及到一些性能上的问题。

控件的选择,按照控件最小功能的标准去选择。例如需要将子节点居中,可以使用 Container 设置 alignment 的方式,也可以使用 Center。但是从功能上,Center 是最小级别的,因此选择它的话,额外的开销会最小。

将 UI 实现了,这只是最基本的,当达到这一步了,应该更多的去思考,如何更好的布局,使得性能更高。

3. 多节点控件

多节点控件的种类就少了一些,虽然也有 11 种,但是功能和场景多了,所以选择上反而会简单一些。

3.1 分类

多节点控件内部实现比单节点控件复杂的多,会从继承以及功能两个方向去做分类。

3.1.1 按照继承划分

从上图可以看出,多节点布局控件基本上可以分为三条线

继承自 BoxScrollView 的控件,有 GridView 以及 ListView;

继承自 MultiChildRenderObjectWidget 的控件,有 Row、Column、Flow、Wrap、Stack、IndexedStack、ListBody、CustomMultiChildLayout 八种;

继承自 RenderObjectWidget 的控件,有 Table 一种。

之前介绍过,GridView 和 ListView 的实现都是非常相似的,基本上就是 silvers 只包含一个 Sliver(GridView 为 SilverGrid、ListVIew 为 SliverList)的 CustomScrollView。 这也是为啥这两元素都继承自 BoxScrollView 的缘故。

MultiChildRenderObjectWidget 类,官方解读如下

A superclass for RenderObjectWidgets that configure RenderObject subclassesthat have a single list of children.

它只是一个含有单一 list 子节点的控件,为什么 Table 不需要继承自 MultiChildRenderObjectWidget 呢?

这是因为 Table 的子节点是二维(横竖)的,而 MultiChildRenderObjectWidget 提供的是一个一维的子节点管理,所以必须继承自 RenderObjectWidget。知道了这些过后,对继承关系的理解会有更好的帮助。

3.1.2 按照功能划分

这个对于多节点布局控件来说,还是比较难以划分的,笔者试着做了如下划分:

列表:GridView、ListView;

单列单行或者多列多行:Row、Column、Flow、Wrap、ListBody、Table;

显示位置相关:Stack、IndexedStack、CustomMultiChildLayout。

个人觉得这种分类方式不是特别的稳妥,但还是写下来了,请大家仁者见仁。

GridView 和 ListView 分为一类,一个是因为其实现非常的相似,另一个原因是这两个控件内容区域可以无限,不像其他控件的内容区域都是固定的,因此将这两个划分为一类。

关于单列单行多列多行的,也并不是说很严格的,Row、Column、Table、ListBody 可能会遵守这种划分,Flow 以及 Wrap 则是近似的多列多行。这种划分绝对不是绝对的,只是个人的一种考量划分方式。

3.2 使用

多节点控件种类较少,而且功能重叠的很少,因此在使用上来说,还是简单一些。比较常用的 GridView、ListView、Row、Column、Stack,这几个控件基本上涵盖了大部分的布局了。

3.3 控件的选择

多节点控件功能重叠的较少,因此选择上,不会存在太多模凌两可的问题,需要什么使用什么即可。

4. 性能优化

性能优化这块儿,可能仁者见仁,并没有一个统一的说法,毕竟现在 Flutter 各方面都还不完善。但是,大方向还是有的,尽量使用功能集更小的控件,这个对于渲染效率上还是有所帮助的。

4.1 优化

在这里我试着去列举一些,并不一定都正确。

对于单节点控件,如果一个布局多个控件都可以完成,则使用功能最小的,可以参照上面控件分类中的功能划分来做取舍;

对于多节点控件,如果单节点控件满足需求的话,则去使用单节点控件进行布局;

对于 ListView,标准构造函数适用于条目比较少的情况,如果条目较多的话,尽量使用 ListView.builder;

对于 GridView,如果需要展示大量的数据的话,尽量使用 GridView.builder;

Flow、Wrap、Row、Column 四个控件,单纯论效率的话,Flow 是最高效的,但是使用起来是最复杂的;

如果是单行或者单列的话,Row、Column 比 Table 更高效;

Stack 和 CustomMultiChildLayout 如果同时满足需求的话,CustomMultiChildLayout 在某些时候效率会更高一些,但是取决于 Delegate 的实现,且使用起来更加的复杂;

上面所列的比较杂,但是归纳起来,无非这几点:

功能越少的控件,效率越高;

ListView 以及 GridView 的 builder 构造函数效率更高;

实现起来比较复杂的控件,效率一般会更高。

4.2 选择

控件的选择,个人觉得把握大方向就够了。如果时间紧急,以实现效率最优先,如果时间充裕的话,可以按照一些优化细则,去做一些选择。单纯控件层面,带来性能上的改进毕竟十分有限。

5. 实战

首先看一下实际的效果图,这个是之前做工程中,比较复杂的一个界面吧,就算放到 native 上看,也是比较复杂的。

这个页面中有不少自定义控件,例如日期选择、进度等。整体看着复杂,实现起来其实也还好。关于如何布局拆解,之前文章有过介绍,在这里不再阐述,诀窍就是一个字 ---- 拆。

5.1 关于自定义控件

自定义控件一般都是继承自 StatelessWidget、StatefulWidget。也有一些特殊的,例如上面的进度控件,直接使用 Canvas 画的。

对于需要更新状态的,一般都是继承自 StatefulWidget,对于不需要更新状态的,使用 StatelessWidget 即可,能够使用 StatelessWidget 的时候,也尽量使用它,StatefulWidget 在页面更新的时候,会存在额外的开销。

Flutter 的自定义控件,写起来可能会比原生的更简单,它更多的是一些基础控件的组合使用,而很少涉及到底层的一些重写。

5.2 关于生命周期

这是很蛋疼的一个问题,一个纯 Flutter 的 App,类似于 Android 中的单 Activity 应用。某个具体的页面就算去监听 native 层的生命周期,也仅仅是获取到 base activity 的,而无法获取到页面层级的。

5.3 感想

Flutter 如果轮子足够的话,还是非常吸引人的,在熟悉了这些基础组件过后,编写起来,速度会非常快。自定义控件的实现,也比较简单。但是,性能方面,还是存在比较大的问题,复杂页面首次载入,速度还是比较慢。对于高端机型来说,整体流畅度很不错,堪比原生的 app,低端机型,表现就比较捉急吧。整体来说,Flutter 表现还是挺不错的,可以上手试试,把玩把玩吧。就是写起来,写着写着就觉得恶心,是真的恶心的那种恶心,看着各种嵌套标签,感觉被降维成了 web 开发。

近期看到一些基于 Flutter 的自动布局解决方案,之前也有想过,完全可以基于 Flutter 做出布局的工具,仅仅是拖拽就可以实现完成度非常高的布局页面。也得益于 Flutter 本身的思想和实现机制,web 方面的很多东西,个人觉得都可以借鉴到 Flutter 上。单纯从 UI 层来说,Flutter 确实有自己独特的地方。如果 Flutter 在最开始,就仅仅是一套跨平台的 UI 的话,可能更容易被人们接受吧。

前几天看了官方的 camera 插件,还是挺蛋疼的,对于国内的 Android 端来说,直接拿来商用几乎是不可能的。插件基于 camera2 去实现,国内大部分厂商对于 camera2 的支持很差,一些很容易复现的 crash 也没有去解决。

如果决定在现有项目中使用 Flutter,则需要做好埋坑造轮子的觉悟。如果人力紧缺的话,不应该在这上面去投入,人力富余的时候,可以投入人力跟进研究,让业界觉得你们很棒很前沿。

6. 后话

笔者建了一个 Flutter 学习相关的项目,Github 地址,里面包含了笔者写的关于 Flutter 学习相关的一些文章,会定期更新,也会上传一些学习 Demo,欢迎大家关注。

7. 参考

Flutter 布局详解

作者:吹个大气球

链接:https://juejin.im/post/5bab35ff5188255c3272c228

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值