Flutter领航系列篇02

作者: 飝狐 | 来源:发表于2019-01-23 22:06 被阅读211次

    上一回 Flutter领航系列篇01 我们对Flutter进行了基本的介绍。
    学了基础控件Scaffold和Text,还学了一个无状态组件StatelessWidget,通过这几个组件构建了一个应用的基本界面。
    这一回我们接着上一回完善侧边抽屉界面,把导航加上,顺便把导航玩儿出一点新花样来。

    1. 有/无状态控件:

    我们先简单讲下上回留下的控件状态话题,让大伙儿对控件状态有个大致的印象。
    在Flutter中控件按状态划分,可以分为两类:

    • StatelessWidget,表示无状态的控件。
      1. 表示纯展示型的控件
      2. 无状态内部必须有一个build函数,用来渲染UI
      3. 每一个项目必须有一个MaterialApp
    • StatefulWidget,表示有状态的控件
      1. 打个比方,如果需要私有数据,例如:
        请求返回的数据,那就需要声明有状态的控件

    按照这个分法,大伙儿可以看出来咱们目前代码继承的是无状态控件。
    好,咱们先热热身,敲个小代码:

    actions: <Widget>[
      IconButton(
        icon: Icon(Icons.search),
        onPressed: () {}
      )
    ],
    

    把这段代码,加入AppBar()。
    按下R键刷新一下,可以看到界面的右上角出现了一个搜索小图标,挺有意思吧。

    2. 抽屉界面:

    接下来,咱们把上回的抽屉界面(drawer)完善一下。
    这里我们需要讲细一点:

    1. listView控件:往drawer()里加入listView控件
    2. children属性:listview里有一个children属性,是一个控件数组,布局元素就放在这个数组里边儿。
    3. UserAccountsDrawerHeader控件:
      • 这里我们可以选择往children控件数组里边儿放入UserAccountsDrawerHeader()控件。
      • UserAccountsDrawerHeader()控件里边儿有两个必填属性:accountEmail、accountName
      • 如果有头像需求,还可以选择加入一个currentAccountPicture属性,如果头像需要是圆形的,那么代码如下:
    currentAccountPicture:CircleAvatar(
      backgroundImage: NetworkImage('[https://qcloud.dpfile.com/pc/bMbudG0HNlcgJ9mCH9uT_KG-8Lkov5fWOq0Vw93525g-OrTcCi-L0gXpZGLYEEcFTYGVDmosZWTLal1WbWRW3A.jpg](https://qcloud.dpfile.com/pc/bMbudG0HNlcgJ9mCH9uT_KG-8Lkov5fWOq0Vw93525g-OrTcCi-L0gXpZGLYEEcFTYGVDmosZWTLal1WbWRW3A.jpg)')
    ),
    

    这里一一解释一下:

    • CircleAvatar:表示圆形头像
    • backgroundImage:背景图
    • NetworkImage:表示是网络图片,这里我在网上随便找了张图

    这个时候大家不妨运行试试看,可以看到效果如下:

    avatar

    目前的背景是蓝色,若是期望再加个背景图的话,可以再加一段儿背景图。
    代码如下:

    decoration: BoxDecoration(
      image: DecorationImage(
        fix: BoxFix.cover,
        image: NetworkImage('https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3672957379,325248742&fm=26&gp=0.jpg')
      )
    ),
    
    • decoration 属性对当前控件的做装饰美化的,他有一个BoxDecoration盒子装饰器,这里边儿的image就是背景图片。
    • fit 属性是控制填充模式的,BoxFix.cover 让图片适配到容器边缘。
      如果不明白,注释掉,对比一下效果就知道了。

    好啦,这个时候,我们接着加入ListTitle()控件,代码如下:

    ListTile(title: Text('我喜欢的'), trailing: Icon(Icons.favorite)),
    ListTile(title: Text('我收藏的'), trailing: Icon(Icons.fastfood)),
    Divider(),
    ListTile(title: Text('个人信息'), trailing: Icon(Icons.face))
    
    • ListTile 控件就是每一项,trailing看英文意思也知道是后面的项,icon组件就不用说了吧。
    • Divider 看英文意思也知道是分割线啦。
    3. 小Tips:
    1. 大家可以看到左上角时间6:35那个地方,是留白的。
      如果不想留白的话,可以往child: ListView()里边儿放一段儿
      padding:EdgeInsets.all(0),
      这里padding的参数不能是数值,不能像前端写css一样直接padding:0
    2. 加了背景图之后,可以看到字是黑色的,效果不太明显。可以在text组件里调整颜色,例如:
    var color = TextStyle(color: Colors.white);
    UserAccountsDrawerHeader(accountEmail: Text('data@data.com', style: color),accountName: Text('飞狐', style: color),
    
    侧边栏

    整体代码如下:

    import 'package:flutter/material.dart';
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        var color = TextStyle(color: Colors.white);
        var avatar = 'https://qcloud.dpfile.com/pc/bMbudG0HNlcgJ9mCH9uT_KG-8Lkov5fWOq0Vw93525g-OrTcCi-L0gXpZGLYEEcFTYGVDmosZWTLal1WbWRW3A.jpg';
        var bgImage = 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3672957379,325248742&fm=26&gp=0.jpg';
        return MaterialApp(
          title: '南帝',
          theme: ThemeData(primarySwatch: Colors.lightBlue),
          home: Scaffold(
            appBar: AppBar(
              title: Text('北丐'),
              actions: <Widget>[
                IconButton(icon: Icon(Icons.search),onPressed: () {})
              ],
            ),
            body: Center(child: Text('中神通')),
            floatingActionButton: FloatingActionButton(onPressed: () {},child: Text('东邪')),
            drawer: Drawer(
              child: ListView(
                padding: EdgeInsets.all(0),
                children: <Widget>[
                  UserAccountsDrawerHeader(accountEmail: Text('data@data.com', style: color),accountName: Text('飞狐', style: color),
                  currentAccountPicture: 
                    CircleAvatar(backgroundImage: NetworkImage(avatar)),
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage(bgImage)
                      )
                    ),
                  ),
                  ListTile(title: Text('我喜欢的'), trailing: Icon(Icons.favorite)),
                  ListTile(title: Text('我收藏的'), trailing: Icon(Icons.fastfood)),
                  Divider(),
                  ListTile(title: Text('个人信息'), trailing: Icon(Icons.face))
                ],
              )
            ),
          ),
        );
      }
    }
    

    侧边栏就告一段落啦,接下来,我们开始写导航栏

    4. 底部导航前奏-代码拆分

    底部的导航实现其实挺简单。不过在写导航之前,咱得先改下结构。
    目的是把 home 控件拎出来,使其独立成一个类,代码如下:

    import 'package:flutter/material.dart';
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: '周伯通',
          theme: ThemeData(primarySwatch: Colors.lightBlue),
          home: MyHomePage()
        );
      }
    }
    class MyHomePage extends StatelessWidget {
      // 省略Scaffold的代码咯...
    }
    

    可以看到,主要就两个步骤:

    1. 新写一个 MyHomePage 类
    2. 把 Scaffold 拆分过去
    5. 底部导航实现

    底部导航的实现有3个动作:

    • 新增底部导航区域控件(bottomNavigationBar),步骤如下:
      1. 位置放置与 drawer 控件同级
      2. 新增 Container 容器
      3. 往 Container 容器里添加 decoration 修饰容器,比如背景色
      4. 高度属性 height
      5. child 属性为 TabBar 组件
    • 新增 DefaultTabController,步骤如下:
      1. 把 Scaffold 控件作为子节点赋值给 child
      2. 同时增加一个 length 属性,该属性值表示导航栏的 item 项长度,如下:

    DefaultTabController(length: 5, child: Scaffold())

    • body 属性改为 TabBarView,放置的就是内容层

    至此,整个完成,代码如下:

    import 'package:flutter/material.dart';
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: '周伯通',
          theme: ThemeData(primarySwatch: Colors.lightBlue),
          home: MyHomePage()
        );
      }
    }
    class MyHomePage extends StatelessWidget {
      var color = TextStyle(color: Colors.white);
      var avatar = 'https://qcloud.dpfile.com/pc/bMbudG0HNlcgJ9mCH9uT_KG-8Lkov5fWOq0Vw93525g-OrTcCi-L0gXpZGLYEEcFTYGVDmosZWTLal1WbWRW3A.jpg';
      var bgImage = 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3672957379,325248742&fm=26&gp=0.jpg';
      @override
      Widget build(BuildContext context) {
        return DefaultTabController(
          length: 5,
          child: Scaffold(
            appBar: AppBar(
              title: Text('北丐'),
              actions: <Widget>[
                IconButton(icon: Icon(Icons.search),onPressed: () {})
              ],
            ),
            body: TabBarView(
              children: <Widget>[
                Text('金融'),
                Text('医疗'),
                Text('中间'),
                Text('量化'),
                Text('我的')
              ],
            ),
            floatingActionButton: FloatingActionButton(onPressed: () {}, child: Text('东邪')),
            drawer: Drawer(
              child: ListView(
                padding: EdgeInsets.all(0),
                children: <Widget>[
                  UserAccountsDrawerHeader(accountEmail: Text('data@data.com', style: color),accountName: Text('飞狐', style: color),
                  currentAccountPicture: 
                    CircleAvatar(backgroundImage: NetworkImage(avatar)),
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage(bgImage)
                      )
                    ),
                  ),
                  ListTile(title: Text('我喜欢的'), trailing: Icon(Icons.favorite)),
                  ListTile(title: Text('我收藏的'), trailing: Icon(Icons.fastfood)),
                  Divider(),
                  ListTile(title: Text('个人信息'), trailing: Icon(Icons.face))
                ],
              )
            ),
            bottomNavigationBar: Container(
              decoration: BoxDecoration(color: Colors.lightBlue),
              height: 60,
              child: TabBar(
                labelStyle: TextStyle(height: 0.5, fontSize: 12),
                tabs: <Widget>[
                  Tab(icon: Icon(Icons.memory), text: '医疗'),
                  Tab(icon: Icon(Icons.money_off), text: '金融'),
                  Tab(icon: Icon(Icons.add_circle, size: 52, color: Colors.white)),
                  Tab(icon: Icon(Icons.high_quality), text: '量化'),
                  Tab(icon: Icon(Icons.my_location), text: '我的')
                ],
              ),
            ),
          ),
        );
      }
    }
    

    大家可以运行看看,导航部分效果如下:


    导航栏
    6. 小彩蛋-底部导航凹凸效果

    有些APP,导航栏中间部分是有凹凸效果的,其实也不难实现。
    飞狐在这里把部分代码给大家演示一下,具体封装成一个很好的控件就是个细活儿了。
    替换 bottomNavigationBar,代码如下:

            bottomNavigationBar: BottomAppBar(
              child: SizedBox(
                height: 60,
                child: Row(
                  mainAxisSize: MainAxisSize.max,
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: <Widget>[
                    Expanded(child: Tab(icon: Icon(Icons.memory), text: '医疗',), flex: 1),
                    Expanded(child: Tab(icon: Icon(Icons.money_off), text: '金融',), flex: 2,),
                    Expanded(child: Tab(icon: Icon(Icons.high_quality), text: '量化',), flex: 2),
                    Expanded(child: Tab(icon: Icon(Icons.my_location), text: '我的',), flex: 1),
                  ],
                ),
              ),
              shape: CircularNotchedRectangle(),
              color: Colors.lightBlueAccent,
            ),
    

    关键点,shape 就是凹陷代码部分。
    还没完,接着把 floatingActionButton 部分代码替换如下:

    floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    floatingActionButton: FloatingActionButton(onPressed: () {}, child: Icon(Icons.add)),
    

    关键点,floatingActionButtonLocation 让浮动图标居中至导航栏中部。
    好啦,大家刷新看效果吧。

    导航栏凹凸效果

    这一回我们讲的控件有点多,难度适中,需要小伙伴儿们好好消化一下。
    复盘一下,这一回我们实现了区分了组件状态,抽屉界面,导航栏。
    下一篇我们会讲到内容列表的实现。

    好啦,下回见。

    相关文章

      网友评论

        本文标题:Flutter领航系列篇02

        本文链接:https://www.haomeiwen.com/subject/eochjqtx.html