Flutter开发实例解析
上QQ阅读APP看书,第一时间看更新

2.3 Flutter组件化

Flutter框架在设计时参考了前端流行的React框架,因此Flutter也是一个组件化框架,在Flutter中一切皆组件(Widget)

组件表示一个可复用的功能单元,可以是UI组件,如文本、图片,也可以是纯功能组件,如路由、状态管理器。需要注意的是,通常组件由Component一词表示,Widget一般表示图形控件。Flutter中的命名习惯有一点特殊,它将Widget作为组件的意思。因此可以理解为React中的Component对应于Flutter中的Widget。

对于组件来说,有3个核心要素。

1)属性:父组件可以向子组件传递一些数据或回调方法,供子组件使用。通过属性,数据能够在组件树中一层层向下传递。

2)状态:位于组件内部,仅供组件内部使用的状态变量。比如一个表单组件,需要将用户输入暂存在状态中,在用户单击“提交”按钮时统一提交。状态通过setState方法更新,更新后会触发布局重新构建。

3)布局构建:在组件中包含build方法,用于构建UI布局。在build方法中,根据父组件传入的属性,以及组件内部的状态,生成对应的布局。每当属性或状态发生变化时,build方法都会重新执行,这是一种响应式布局开发模式。

在Flutter中提供了3大类组件:StatelessWidget、StatefulWidget、InheritedWidget,其中前两种最为常用,在本节中进行讲解。第三种将在后续章节中进行介绍。

2.3.1 组件化思想

传统移动端开发是基于页面加视图的概念来开发的。比如在Android中,页面的载体是Activity,页面的视图元素则由众多视图(View)组成。

随着前端React、Vue的流行,它们所采取的组件化思想迅速流行起来。组件化思想概括来说就是“一切皆组件”,在应用中不再区别页面与视图,统统都是组件。

前端组件化相较于传统移动端开发,具备一定优势,具体包括以下几点。

1)概念简化:在传统的Android开发中,需要了解不同的概念,如Activity、Application、ViewGroup等,只有把这些都研究透彻才能够进行开发。而在组件化架构中,这些概念都被统一成组件,页面也是组件、视图也是组件,它们以同样的原理工作。通过这种简化,降低了框架的复杂性,也降低了开发者的学习成本。

2)函数式思想:组件化架构通过统一概念,可以以一种函数式嵌套的形式进行搭建。这种搭建方式相较于传统架构方式,不仅更加优雅清晰,同时为更高级的特性提供了支撑。

3)声明式UI:在传统开发中,开发者需要手动维护界面元素的布局和内容,尤其是涉及元素增减时,开发者需要自己知道哪些要改变。当数据变化时,开发者也要手动更新对应的视图元素。有了声明式UI,开发者只需要维护组件间的关系,以及建立组件与状态间的响应式关联。当视图需要改变时,通过Virtual DOM机制,框架会自动进行视图变更。同时基于响应式订阅,数据变化时视图组件能够自动更新,大大提高了开发效率。

目前业界普遍认可组件化思想是一种更为先进的应用开发模式,能够大大提高开发效率。

2.3.2 无状态组件StatelessWidget

StatelessWidget是最简单的组件形式,它不包含状态,仅接收来自父组件的输入属性,并构建布局展示。无状态组件在实际中被大量应用,由于仅有输入、输出,不保存中间状态,行为像函数一样,在同类的React框架中将这种组件称为函数式组件

无状态组件继承StatelessWidget类,并覆写build方法,在build方法中进行布局创建。比如创建标题组件(H1、H2),分别表示不同等级的标题,具体代码如下:

在实际使用时,用一个纵向排列组件的Column布局,对H1和H2组件的实例进行封装,并分别向两个标题组件传入不同的输入文本,具体代码如下:

运行代码,效果如图2-2所示。

图2-2 无状态组件示例

2.3.3 有状态组件StatefulWidget

StatefulWidget表示含有状态的组件,实际中也被大量应用。在Flutter中要创建有状态组件,首先从StatefulWidget中派生一个类,并覆写类的createState方法。之后要再基于State类派生一个状态类,并将这个类的实例作为createState方法的返回值。在状态类中进行两项工作,首先是定义状态,并覆写build方法进行布局搭建。具体代码示例如下:

在上面的代码中,可以看到在StatefulWidget中,布局构建build作为State类的方法,这是Flutter中的一个特点,也是与其他框架(如React)的使用差异。在Flutter中,StatefulWidget类只用于接收输入属性,而状态维护与布局渲染均放到State类中进行。运行上述代码,代码执行效果如图2-3所示。

在上述代码运行程序中,最开始屏幕上仅有一个“+1”按钮,随着不断单击,每次单击totalCount的值会递增,并通过setState方法更新状态,状态更新又会触发build重新构建,在build重新构建中,会根据totalCount的值判断“Bingo”文本是否展示。

2.3.4 组件的生命周期

组件生命周期指的是组件创建、展示、销毁的过程。这个过程需要在合适的时机进行初始化、资源释放等操作。如果在组件销毁时没有将该释放的资源释放,垃圾回收器(GC)就很可能无法回收组件,从而造成内存泄漏问题。

图2-3 有状态组件示例

生命周期概念只存在于StatefulWidget,StatelessWidget是没有生命周期概念的。在首次创建StatefulWidget组件时,首先会调用其构造方法,之后调用createState方法创建状态实例。

StatefulWidget包含有以下生命周期方法。

1 createState状态创建生命周期

创建状态实例,在组件的创建过程中只调用一次。代码实现如下:

2 initState状态初始化生命周期

initState是State中的方法,是组件的创建生命周期,在组件创建过程中只被调用一次。需要注意的是,在这个方法中还无法使用BuildContext。在实际应用中,通常在这个方法里发起网络请求,或订阅状态管理器的数据。代码实现如下:

3 didChangeDependencies依赖变化生命周期

当组件第一次创建时,didChangeDependencies会在initState之后调用。除此之外,如果组件依赖InheritedWidget,当InheritedWidget发生变化时,会触发didChangeDependencies方法。

4 build布局构建生命周期

build方法用于创建页面布局。组件第一次创建的时候,会调用一次build方法。之后每当状态发生改变时build都会被调用。有两种方式能触发状态改变,一是使用setState更新状态,二是didUpdateWidget被调用。

5 didUpdateWidget组件更新生命周期

当父组件触发重建时会触发子组件的didUpdateWidget。在didUpdateWidget方法中,会以参数的形式传入旧组件,供开发者进行对比。

6 setState状态更新方法

setState方法用于进行状态更新,将要更新的状态传入setState中进行更新,会触发调用build方法更新布局。例如,在前面的计数器示例中,调用setState更新累计计数,代码实现如下:

7 deactivate组件失活生命周期

当组件将要从组件树中删除时触发,意味着组件将要被销毁。但这不表示组件一定会被销毁,比如当组件从组件树中移除,触发了deactivate生命周期,随后在当前帧结束之前又被插入回组件树,此时尽管调用了deactivate方法,但事实上组件实例并没有被销毁,依旧在组件树上。

8 dispose销毁生命周期

当组件被永远从状态树中移除时触发,表示组件被销毁。通常在这个方法中进行一些资源释放操作,如释放监听、定时器等。

9 StatefulWidget生命周期流转图

StatefulWidget从创建到最终销毁的各个生命周期,可以梳理为整体生命周期流转图,如图2-4所示。在实际开发中,build是最常用的生命周期,用于创建组件的布局。initState和dispose也经常使用,用于状态的初始化与销毁。需要注意的是,一些在initState中创建的对象,如定时器、监听等,需要在dispose时及时释放,从而避免内存泄漏。当需要更新状态时,调用setState方法,会出发build方法进行布局更新。

图2-4 StatefulWidget生命周期流转图

需要注意的是,生命周期系列方法中不包含状态类的构造函数,在状态类的构造函数中访问不到Widget的输入属性。

2.3.5 Material和Cupertino组件库

Flutter提供了两套UI组件库,Material组件库和Cupertino组件库,前者用于开发Android Material Design设计风格应用,后者用于开发iOS风格应用。采用哪套组件库并不会影响跨端,开发者可根据自身应用的设计风格进行选择。

由于Flutter采用自绘制,因此这两套组件库都非系统原生,而是Flutter自己又实现了一遍。两套组件库实现的质量很高,以至于做到与原生UI难以分辨的程度。

做到这种程度是非常不容易的,试想如何才能通过自绘制高度还原一个平台的使用体验呢?首先控件要长得像,比如按钮、文本框等UI要与目标系统保持一致。控件长得像就做到高度还原了吗?还不够。不同平台还有一些细腻的交互效果,比如整个应用的页面过场方式,默认字体、主题等。把这些都做到才能真的实现以假乱真。

Flutter的组件库设计非常细致,两套组件库都实现了以假乱真的效果。这两套组件库各提供了大量组件,但整体来说可以归为三类:应用级组件、页面级组件、视图组件

1 应用级组件

在Flutter组件树中,最根级组件通常为WidgetsApp组件,用于提供App全局通用能力,比如路由、多语言、字体主题等。WidgetsApp是一个基类,两套组件库均基于它进行派生,Material组件库对应的为MaterialApp组件,Cupertino组件库对应的为CupertinoApp组件。这两套组件在WidgetsApp组件的基础上,对目标平台下的默认字体、主题、过场动效等进行了详细适配。

2 页面级组件

在不同的设计风格下,页面有着固定的展示方式。比如Material Design设计风格下的页面,标题栏始终位于顶部,并且具有特定UI样式,Fab位置始终位于右下方,且距离页面边距固定。对于这种固定的页面组织形式,如果由开发者自行实现,会增加开发者的工作量,同时不同人开发出来的代码展示效果也难以实现统一。

因此在Flutter组件库中,提供了用于页面架构布局的组件Scaffold。Scaffold意为页面脚手架,Flutter为开发者搭建好了页面框架,在使用时只需要填充设置项即可。这样大大提高了开发效率,可以方便、快速地开发出高质量页面。

由于不同平台页面展示风格不同,在两套组件库中分别提供了Scaffold组件。Material组件库对应组件为Scaffold组件。Cupertino组件库对应组件为CupertinoPageScaffold、CupertinoTab-Scaffold

3 视图级组件

组件库中绝大多数组件都是视图组件,用于展示屏幕中的不同视图元素,比如按钮、文本框、对话框等。两套组件库分别提供了各个平台下的视图组件。比如按钮组件,在Cupertino下提供有CupertinoButton,在Material下提供有FlatButton、IconButton、FloatingActionButton、OutlineButton、RaisedButton等。两套组件库提供的组件可在官方文档站中查看。

Material组件库: https://flutter.dev/docs/development/ui/widgets/material。

Cupertino组件库: https://flutter.dev/docs/development/ui/widgets/cupertino。

通过对比可以看出,Material组件库中的组件要远比Cupertino组件库丰富。原因在于Flut-ter和Material Design都由Google出品,Material组件库由Material Design官方团队进行维护,因此质量相对更高。

使用了一个组件库后,是不是就不能使用另一个组件库中的组件了呢?并不是,组件库组件是支持混用的。组件库组件实际上都是Flutter组件,也就是说,不论是Material组件库中的按钮,还是Cupertino组件库中的按钮,它们的底层实现机制是相同的。在实际开发中组件混用也是经常采用的,比如本书中技术头条实例,就在MaterialApp中使用了Cupertino搜索框组件,以实现更好的UI展现效果。