经过 2 周的学习, 看过笔记 1-8 的小伙伴们已经有不少开始自己写 APP 了, 我也按耐不住这股热情, 想要自己开发个 APP 玩玩, so, 从本篇起, 仿造一个 APP, 项目从 0 开始, 每篇增加一些内容, 一点一点完成这个 APP, 每次迭代的代码都将上传到我的 git 仓库
鉴于我 2 周多的 Flutter 代码经验, 代码结构的思维可能没有多年开发经验的老鸟稳, 如果有写的不好的地方请大家多多指教
如上图所示, 本篇将搭建一个 HomePage, 再其左上角加入侧边栏入口, 并且通过侧边栏可以进入其他页面
- ## 第一步 创建项目和文件夹打开 vscode, 到一个路径下输入命令:
- flutter create appbyflutter
根据图中所示, 将项目目录准备好:
由于第一篇开发用到的东西不多, 先简单向项目目录中添加一个 images 文件, 用于存放 APP 默认图片默认的 lib 文件夹下添加一个 pages 文件夹, 用于存放每个页面
## 第二步 将 main.dart 仅作为 APP 的入口, 承担页面入口和路由的功能:
由于 APP 不只有一个页面, 为了方便维护和管理, 所有的页面代码都转移到 pages 文件夹下, main.dart 中处理 APP 的主页面入口路由和一系列需要初始化 (如自动登陆入场动画等) 的任务有过 vuereact 开发经验的前端大神们应该不陌生, 这样做可以使主程序和页面解耦, 当然本篇还没有用到路由, 暂不书写路由的代码, 等不及要了解路由的同学可以参考前端高手偏罗的第一个 APP 或者英文阅读理解
## 第三步 ### 主页面 如第一步的图所示, 在 pages 文件夹中添加了 2 个文件: home_page.dart 和 other_page.dart, 其中 home_page.dart 是这个 APP 的主页面, other_page.dart 作为的以后再开发的页面
注意在第二步的 runapp()函数中, 用到了 MaterialApp(), 意味着程序 APP 所有的页面控件默认配套_Material_风格
由于主页面会动态引用各种控件, 因此_StatefulWidget_类型才可以满足页面需求从下图中分解一下页面结构:
先看图左中有状态控件 HomePage 为整个页面的最顶层包裹, 其内放入了一个 Scaffold 脚手架, Scaffold 中有非常丰富的属性, 可以放入侧边栏按钮 Drawer 控件页面标题 AppBar 控件和 body 部分, 于是贴入以下代码:
- import 'package:flutter/material.dart';
- class HomePage extends StatefulWidget {
- @override
- _HomePageState createState() => new _HomePageState();
- }
- class _HomePageState extends State<HomePage> {
- @override
- Widget build(BuildContext context) {
- return new Scaffold(
- appBar: new AppBar(title: new Text("CYC"), backgroundColor: Colors.redAccent,), // 头部的标题 AppBar
- drawer: new Drawer(), // 侧边栏按钮 Drawer
- body: new Center( // 中央内容部分 body
- child: new Text('HomePage',style: new TextStyle(fontSize: 35.0),),
- ),
- );
- }
- }
OK, 左图的页面就这么轻松搭建完毕要实现右图中的展开的侧边栏, 很简单, 向 Drawer 控件中塞东西吧
### 侧边栏 我们先图解一下侧边栏的结构:
整个侧边栏主从上到下按区块分别放置了账号和若干功能项 + 分割线的列表, 很容易想到使用布局控件 ListView
账号信息区域中有账号头像粉丝头像账号文字信息和背景图, 这块我们可以使用 Material 控件库的
UserAccountsDrawerHeader
控件实现
下面的功能列表项目不用多说, ListTitle 控件妥妥的, 分割线直接 Divider 即可
于是, 我们向 new Drawer()中加入如下代码:
- // 侧边栏填充内容
- drawer: new Drawer(
- child: new ListView(
- children: <Widget>[
- new UserAccountsDrawerHeader( //Material 内置控件
- accountName: new Text('CYC'), // 用户名
- accountEmail: new Text('example@126.com'), // 用户邮箱
- currentAccountPicture: new GestureDetector( // 用户头像
- onTap: () => print('current user'),
- child: new CircleAvatar( // 圆形图标控件
- // 图片调取自网络
- backgroundImage: new NetworkImage('https://upload.jianshu.io/users/upload_avatars/7700793/dbcf94ba-9e63-4fcf-aa77-361644dd5a87?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240'),
- ),
- ),
- otherAccountsPictures: <Widget>[ // 粉丝头像
- new GestureDetector( // 手势探测器, 可以识别各种手势, 这里只用到了 onTap
- onTap: () => print('other user'), // 暂且先打印一下信息吧, 以后再添加跳转页面的逻辑
- child: new CircleAvatar(
- backgroundImage: new NetworkImage('https://upload.jianshu.io/users/upload_avatars/10878817/240ab127-e41b-496b-80d6-fc6c0c99f291?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240'),
- ),
- ),
- new GestureDetector(
- onTap: () => print('other user'),
- child: new CircleAvatar(
- backgroundImage: new NetworkImage('https://upload.jianshu.io/users/upload_avatars/8346438/e3e45f12-b3c2-45a1-95ac-a608fa3b8960?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240'),
- ),
- ),
- ],
- decoration: new BoxDecoration( // 用一个 BoxDecoration 装饰器提供背景图片
- image: new DecorationImage(
- fit: BoxFit.fill,
- image: new NetworkImage('https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg')
- // 可以试试图片调取自本地调用本地资源, 需要到 pubspec.yaml 中配置文件路径
- // image: new ExactAssetImage('images/lake.jpg'),
- ),
- ),
- ),
- new ListTile( // 第一个功能项
- title: new Text('First Page'),
- trailing: new Icon(Icons.arrow_upward),
- }
- ),
- new ListTile( // 第二个功能项
- title: new Text('Second Page'),
- trailing: new Icon(Icons.arrow_right),
- }
- ),
- new Divider(), // 分割线控件
- new ListTile( // 退出按钮
- title: new Text('Close'),
- trailing: new Icon(Icons.cancel),
- onTap: () => Navigator.of(context).pop(), // 点击后收起侧边栏
- ),
- ],
- ),
- ),
上面的代码, 用到了很多陌生的控件, 如 UserAccountsDrawerHeaderGestureDetectorBoxDecorationNetworkImageExactAssetImage 等等, 这里我就不一一介绍了, 各自的特性和用法请参考官方阅读理解题库, 刚开始我也是懵逼的, 这些内置控件大家简单背诵一下即可, 有可能后面因为页面复杂度的提高, 单独拿出来封装也说不定, 会使用就可以了
大家可以试试从屏幕的左边沿向右滑动的手势, 是不是发现可以拉出侧边栏? 再向右滑动收回侧边栏我并没有添加任何手势事件的代码, 这是 Drawer 控件自带的属性, 和控件自带 Material 风格动效一样, 内置控件也自带了默认手势, 隐隐听到~ 原生开发的程序员哭晕在厕所, 哈哈哈
## 第四步 ### 功能按钮触发页面跳转 首先我们要创建一个子页面, 于是乎 pages 文件夹下, 我又创建了一个 other_page.dart 文件要从 HomePage.dart 中跳转到 other_page.dart, 还需要在 HomePage.dart 中引一下 other_page.dart 于是:
然后到 other_page.dart 中敲入代码:
- import 'package:flutter/material.dart';
- class OtherPage extends StatelessWidget {
- final String pageText; // 定义一个常量, 用于保存跳转进来获取到的参数
- OtherPage(this.pageText); // 构造函数, 获取参数
- @override
- Widget build(BuildContext context) {
- return new Scaffold(
- appBar: new AppBar(title: new Text(pageText),), // 将参数当作页面标题
- body: new Center(
- child: new Text('pageText'),
- ),
- );
- }
- }
Flutter 要求转入的页面必须提前定义一个常量分配好空间, 且在构造函数中植入这个参数, 才可捕捉外部传过来的参数值
- ### 触发跳转 向 First Page 和 Second Page 这两个 ListTile 控件中加入点击跳转页面的代码:
- new ListTile(
- title: new Text('First Page'),
- trailing: new Icon(Icons.arrow_upward),
- onTap: () {
- Navigator.of(context).pop(); // 点击后收起侧边栏
- Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new OtherPage('First Page'))); // 进入 OtherPage 页面, 传入参数 First Page
- }
- ),
- new ListTile(
- title: new Text('Second Page'),
- trailing: new Icon(Icons.arrow_right),
- onTap: () {
- Navigator.of(context).pop();
- Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new OtherPage('Second Page')));
- }
- ),
上面的代码中 onTap()事件里有一句
Navigator.of(context).pop();
, 意味着先收起侧边栏, 再进入新页面如果没有这句代码, 即使进入了新页面, 再返回来, 侧边栏依然处于展开的样子, 这个体验是反人类的, 所以写上它吧~ 少年
## 总结 由于我没有详细的去定位和设计产品到底是干什么的, 大家可能会觉得有点懵逼, 为什么是这种侧边栏的布局, 而不是很多社交 APP 常用的顶部 + 底部 Tab 栏的样式, 不着急, 我们下一篇实现侧边栏有什么好处呢? 节省空间, 如果底部需要放置更重要的功能控件 (比如音乐播放器) 时, 往侧边栏放入页面切换逻辑是个不错的应对方案本篇内容其实非常简单, 主要就是介绍大家认识几个常用控件, 不用调 CSS, 不用思考因为冒泡事件导致复杂的交互逻辑实现, 这就是 Flutter 的魅力, 简约而不简单, 相信大家看过之后, 自行开发 APP 的信心更足了
来源: https://juejin.im/post/5aad44d05188252c321973a1