前言
在上一篇文章 Flutter 系列:2. 实现一个简单的登录界面通过一个简单的登录页面带入了 Flutter 中页面构建的方式以及一些简单控件的使用;在开发一个 app 前首要的任务往往是搭建 app 需要的基础结构,比如底部菜单,路由导航,网络请求以及一些常用的颜色、图标、按钮、toast 组件等。
本次的 demo 将实现一个简单的 app 所需的基础结构,实现一个简单的 app,基于底部 TabBar 的方式模块切分,实现网络层调用豆瓣 api 展示电影列表,任意界面登录验证,app 如下图。
[GitHub 源码传送]
TabBar 菜单
目前 app 设计中大部分 app 都是由底部 TabBar 菜单 + 顶部导航信息的方式构建的,在 iOS 开发中 UITabBarController 和 UINavigationController 几乎是 APP 的标配, 同样在 Flutter 中基于 Scaffold 的构建方式也直接提供了 appBar+body+bottomNavigationBar 的方式来切分导航栏、内容和底部菜单, 所以我们只需要在首页的 Scaffold 构造中传入 bottomNavigationBar 即可。
在 Flutter 中为我们提供了 material design 风格的 BottomNavigationBar 和 iOS 风格的 CupertinoTabBar,我们只需要选择其一稍作封装即可,本 demo 选择 CupertinoTabBar,并封装到 BottomNavWidget 中,相关细节请看源码。
body 的切换
虽然 Scaffold 提供了 appBar+body+bottomNavigationBar 的组合,但是并没有实现 bottomNavigationBar 点击切换 body 页面显示功能,所以需要开发者自己去处理 bottomNavigationBar 的点击回调来动态切换 body 中的内容。
不同的 bottomNavigationBarItem 对应着不同的显示页面,电影 tabBar 对应显示电影列表页面,发现 tabBar 对应显示发现页面 … 他们都在 body 中,他们之间有着频繁的切换但同时只能显示一个页面;基于此使用 Stack 布局的方式来实现,每个页面组成一个数组成为 Stack Widget 的 children 并缓存避免重复创建,使用 Offstage 组件来包装每个 tab 页面,并将 bottomNavigationBar 当前选择的 index 对应的页面的 offstage 设置为 false, 这样只有当前选择的 tab 对应的页面显示在 body 中,而其他的界面并不会显示也不会接收事件占用空间。
路由导航
路由导航也是 app 常见的基础功能,服务器通过下发路由信息可以实现动态的控制 app 的页面跳转,常用于动态页面,push 和 web 跳转。
Flutter 中的导航有点类似 iOS 的方式,都是通过栈的方式来管理路由页面。Navigator 就是 Flutter 中管理导航路线的 Widget,注意 Navigator 管理的是页面导航的路线,称为 Route 的东西而不是像 iOS 中直接管理的 controller,而每个 Route(CupertinoPageRoute) 则可以通过 builder 来指定显示的 Widget,同时 Navigator 也提供了对 Route 栈操作的方法,push 和 pop。
Navigator 管理的对象是 Route,Flutter 提供了 MaterialPageRoute 和 iOS 风格的 CupertinoPageRoute,MaterialPageRoute 是根据手机平台自动调整页面的出现动画,本 Demo 选用 CupertinoPageRoute 以从右到左的页面出现动画,然后指定其 builder 即可实现页面的跳转。
MaterialApp 内置了一个顶层的导航器 Navigator,routes 属性支持配置静态的路由表,如果在 routes 中找不到对应的路由配置时则调用 onGenerateRoute 来支持动态的路由跳转,它的定义如下:
所以我们需要通过一个函数来实现 MaterialApp 的 onGenerateRoute 就可以根据 RouteSettings 中的路由信息动态的生成页面的 Route,同时以 Uri 的方式来指定 Route 的名称就可以实现动态传参了,具体详见 Demo 源码中 RouteManager 类。
登录注册
登录注册页面可能在 app 的任何页面推出,同时可能不支持返回需要强制登录的情况,在 iOS 中常常以 present 的方式出现,所以在 Flutter 中需要指定 CupertinoPageRoute 的 fullscreenDialog 属性为 true 即可
页面的跳转
在 iOS 的开发中基于 UITabBarController 和 UINavigationController 的构建方式中页面跳转是在 UINavigationController 内跳转的,同时通过设置 Controller 的 hidesBottomBarWhenPushed 属性支持动态的显示和隐藏底部的 TarBar, 每个 TabBar 对应的是一个独立控制的 UINavigationController,他们各自有自己路由的导航栈,在 Flutter 中提供的 CupertinoTabScaffold 通过为每个 TabBar 指定显示为 CupertinoTabView 来实现了同样的机制。
往往在开发中进入二级界面后底部的导航栏都是隐藏的,所以我们完全可以只使用 MaterialApp 内置的顶层 Navigator 来实现我们的导航控制,本 Demo 也是如此。
网络请求
移动端的网络环境是千变万化的,所以 app 的网络请求应该是一个异步的过程,不能阻塞主线程,本 Demo 是基于 Dart 的第三方 Http 网络请求库 dio。
dio 是一个强大的 Dart Http 请求库,支持 Restful API、FormData、拦截器、请求取消、Cookie 管理、文件上传 / 下载、超时等 …
网络层实现了通过 dio 请求到网络数据然后反序列为 Model 对象,dart 中的 json 反序列化要比其他语言麻烦,借助的是 json_annotation 这个库。
从请求 api 到回调再到反序列为 Model 对象这个过程都应该是一个异步的过程,所以他们返回的都是一个 Futrure 对象,使用 Completer 就可以很方便的生成一个 Future, 然后在恰当的时候传入数据或者错误来结束这个 Future。
列表
列表的展示是基于 FutureBuilder 的方式,因为其依赖 api 请求返回的 future,当 future 的状态变更时 FutureBuilder 会接收到最新的快照信息 AsyncSnapshot,通过其当前快照来控制 ListView 或者 CircularProgressIndicator 的显示。
其他
App 开发中还有许多其他的基础模块,比如和原生通信组件 (channel)、图片组件、日志组件、其他公共的弹窗、上下拉刷新组件等,本 Demo 还来不及一一实现,随着学习的深入以后再慢慢总结吧,有不妥的地方还望指正。
Demo 源码地址:[GitHub 源码传送]