flutter实战1:完成一个有侧边栏的主界面

经过2周的学习,看过笔记1-8的小伙伴们已经有不少开始自己写APP了,我也按耐不住这股热情,想要自己开发个APP玩玩,so,从本篇起,仿造一个APP,项目从0开始,每篇增加一些内容,一点一点完成这个APP,每次迭代的代码都将上传到我的git仓库。

鉴于我2周多的Flutter代码经验,代码结构的思维可能没有多年开发经验的老鸟稳,如果有写的不好的地方请大家多多指教。

本篇需要完成的任务

如上图所示, 本篇将搭建一个HomePage,再其左上角加入侧边栏入口,并且通过侧边栏可以进入其他页面。

第一步

创建项目和文件夹。打开vscode,到一个路径下输入命令:

flutter create appbyflutter

根据图中所示,将项目目录准备好:


程序目录

由于第一篇开发用到的东西不多,先简单向项目目录中添加一个images文件,用于存放APP默认图片。默认的lib文件夹下添加一个pages文件夹,用于存放每个页面。

第二步

main.dart仅作为APP的入口,承担页面入口和路由的功能:

main.dart不写页面代码

由于APP不只有一个页面,为了方便维护和管理,所有的页面代码都转移到pages文件夹下,main.dart中处理APP的主页面入口、路由和一系列需要初始化(如自动登陆、入场动画等)的任务。有过vue、react开发经验的前端大神们应该不陌生,这样做可以使主程序和页面解耦,当然本篇还没有用到路由,暂不书写路由的代码,等不及要了解路由的同学可以参考前端高手偏罗第一个APP或者英文阅读理解

第三步

主页面

如第一步的图所示,在pages文件夹中添加了2个文件:home_page.dartother_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(     //侧边栏按钮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),
              onTap: () {
                Navigator.of(context).pop();
                Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new SidebarPage()));
              }
            ),
            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 SidebarPage()));
              } 
            ),
            new ListTile(   //第二个功能项
              title: new Text('Second Page'),
              trailing: new Icon(Icons.arrow_right),
              onTap: () {
                Navigator.of(context).pop();
                Navigator.of(context).pushNamed('/a');
              } 
            ),
            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 PageSecond 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的信心更足了,好勒,今天就到这里,感谢大家的支持,请关注我的Flutter圈子,多多投稿,也可以加入flutter 中文社区(官方QQ群:338252156)共同成长,谢谢大家~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容