
2.1 MaterialApp
MaterialApp组件是Material Design设计风格在Flutter中的体现,常用于开发Material Design设计风格的APP。一般情况下,一个应用程序中存在一个MaterialApp组件是合理的,所以MaterialApp组件常用在Widgets树结构的顶层,或者可以理解为用来构建应用的根布局。如图2-1所示,在创建的默认Flutter项目中,MaterialApp是被作为第一个(根视图)来创建的,对应的代码如代码清单2-1所示。

2.1.1 路由配置
在应用程序开发中,路由可理解为在屏幕上渲染显示的页面。在Android原生开发中,要打开一个新的页面,先初始化一个Intent,绑定目标页面的class,然后调用Context的startActivity方法。在iOS原生开发中,要打开一个新的页面,首先创建一个ViewController对象,然后用pushViewController推出一个新的页面。
在Flutter中,打开一个新的页面有两种方式,一种是静态路由,一种是动态路由。
静态路由,在MaterialApp组件中通过routes来配置跳转规则,routes配置的是一个Map,这个Map中key对应路由名称。value对应路由页面,如代码清单2-2所示。

然后通过Navigator的pushNamed方法打开对应的页面。通过静态路由的方式打开页面SecondPage,代码如下。

动态路由不通过routes配置,无指定的路由名字,在使用时直接使用Navigator来打开,代码如下。

要退出当前Widget页面,有三种方式:第一种是点击手机的返回按钮(物理按钮,安卓手机),或者是在全面屏手机上左侧边缘或者右侧边缘手势滑动退出,在iOS手机上手势由左向右滑动退出;第二种是点击页面AppBar中默认的左上角的返回箭头;第三种就是在页面中通过Navigator的pop方法主动调用退出页面Widget的方式。
调用Navigator的pop方法,可以理解为将页面推出路由栈,如果需要回传参数,可以直接写在pop方法中,需要注意的是,在回传数据与接收数据的地方,数据类型要保持一致,也需要注意在关闭当前页面时,最好判断一下当前页面是否是页面栈中的最后一个页面,也就是Widgets树中的最后一个Widget,如果是的话,再强行执行pop方法,那么栈中就会无Widget页面,导致页面黑屏。所以安全的代码如下。

Navigator也提供了便捷的方法来关闭页面,代码如下。

Navigator的maybePop方法在关闭页面时,会自动判断是否可关闭。如果当前页面是Widgets树中的最后一个页面,就不进行操作,否则就直接关闭当前页面。
一般在实际项目中会有这样的使用场景:A页面将参数传递给B页面,然后B页面在关闭时再将一个处理结果值传递给A页面,代码清单2-3所示即通过静态路由的方式配置两个页面。

定义A页面(Example202A)代码如下。


2-1 A页面定义效果图
在静态路由使用场景下进行传值,首先在A页面中定义参数,参数类型可以是任意的,在这里使用一个Map<String,String>,然后跳转至B页面,传递参数,代码如下。

如果需要获取第二个页面关闭时回传到第一个页面的数据,可以使用then函数,代码如下。


然后在B页面(Example202B)中定义一个退出按钮与一个显示接收到的数据的Text组件,如代码清单2-5所示。


2-2 B页面定义效果图

在B页面中获取A页面传递的数据需要在didChangeDependencies生命周期中获取,因为这里使用了context,所以不能在initState方法中获取,原因是在initState中context还未绑定成功。然后单击B页面中的“关闭当前页面”按钮关闭B页面,并回传数据,回到A页面后,获取回传的数据,显示如图2-2所示。

图2-2 A页面显示B页面回传参数效果图
在动态路由中传值就比较方便了,可直接通过构造函数传值,代码如下。


然后在页面Example202C通过插值法来直接获取数据,代码如下。

MaterialApp组件中通过home属性来配置默认显示的页面,如代码清单2-1中,home属性配置的MyHomePage就是当前Flutter应用启动加载的第一个Flutter页面。
配置Flutter启动页面的方式二是在MaterialApp组件的属性routes中配置“/”,然后指向需要默认打开的页面,routes接收的是一个Map,这种配置方式称为静态路由配置,代码如下。


配置Flutter默认启动页面的方式三是在MaterialApp组件的initialRoute属性中指定初始化打开的页面对应的路由,代码如下。

需要注意的是,在代码清单2-8所示的配置方式三中,initialRoute指定的是静态路由的名称,这个名称需要在routes中配置后才能使用,如果没有配置,而是直接使用,就会抛出如下异常。

需要注意的是,这三种方式是互斥的,只允许单独配置使用。如果同时配置了routes中的“/”与initialRoute,那么当运行程序时会抛出如下异常。


配置Flutter默认启动页面的总结如下。
1)通过MaterialApp的home属性来配置,直接创建页面对象即可。
2)通过在MaterialApp的routes属性中配置“/”来指向显示的页面对象。
3)通过MaterialApp的initialRoute属性来指向routes属性配置的静态路由名称。
CupertinoApp组件与MaterialApp组件是同一个级别的,也可以通过以上三种方式来配置默认显示的页面。不同的是CupertinoApp构建的是苹果设计风格的应用程序,在CupertinoApp组件之下,需要使用Cupertino系列的组件来构建应用。
2.1.2 语言环境与主题配置
在默认创建的Flutter应用的文本输入框TextField中,长按显示的复制与粘贴、日期控件的界面是英文的,即使手机系统设置的是中文语言环境,显示仍然是英文的。如果需要配置为中文显示,首先需要添加Flutter应用的多语言功能支持。在配置文件pubspec.yaml中添加localizations多语言环境支持,代码如下。

然后在根布局视图下的MaterialApp组件中设置中文语言环境,代码如下。

ThemeData是在Flutter中对各组件样式的封装,在Flutter中通过MaterialApp的theme属性来配置,代码如下。

在上述代码中用到了theme与darkTheme,这两个属性都是来配置应用的主题色的,themeMode是用来配置当前主题色的方案的,取值可为ThemeMode.system跟随当前系统的主题色、ThemeMode.light使用亮色主题、ThemeMode.dark使用暗色主题。
ThemeData的primarySwatch属性可以理解为一个基本的主要的配色设定,也就是说,当在ThemeData中单独指定primarySwatch后,没有指定其他颜色配置时,其他如primaryColor等11个属性会引用primarySwatch配置的颜色值。
从源码中得知,当primarySwatch没有主动指定颜色时,程序默认取Colors.blue,所以在创建Flutter默认工程时,生成的默认页面中大部分组件是蓝色系的颜色。
primaryColor可以理解为用来配置主颜色,AppBar标题栏、TabBar等一般是一个Widget页面的头,所以primaryColor配置的默认颜色为这些组件配置默认取用的背景颜色。
accentColor可理解为引人注意的颜色,也可以理解为当前活跃的颜色,如TabBar的indicator指示线的颜色、开关Switch被选中后显示的高亮颜色等。
ThemeData中的highlightColor是高亮颜色,如按下Button时显示的高亮颜色。
splashColor是点击反馈水波纹效果的颜色配置,例如点按Button显示的水波纹扩散的颜色。
当然还有许多配置不同使用场景下的Widget样式,详见表2-1。
表2-1 ThemeData其他属性一览

(续)
