Flutter第六章(BottomNavigationBar ,AppBar,TabBar ,TabController 以及案例)

版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!

情感语录:如果你想得到从未拥有过的东西,那么你必须去做从未做过的事情。

欢迎来到本章节,上一章节我们讲了路由 还记得吗?知识点回顾 戳这里 Flutter基础第五章

本章主要围绕底部导航条顶部导航条 以及自定义AppBar 和优化方案TabController进行讲解,现在无论什么应用底部导航菜单和顶部导航菜单似乎都成了标配 ;有关这样的应用可以说是多不胜数"QQ、微信、头条、京东、饿了么等等....."

本章简要:

1、BottomNavigationBar 底部导航条

2、 AppBar 自定义顶部样式

3、 TabBar 的样式配置

4、 TabController 的使用

一、BottomNavigationBar

BottomNavigationBar 是底部导航条,可以让我们定义底部 Tab 切换,BottomNavigationBar是 Scaffold组件的参数。抛开IOS不说,因为我是做Android开发的,在Android原生中你没有封装有自己的底部导航栏菜单模块,你可能最常用的就是 Activity + RadioButton + Fragment的方式去实现了,这对于刚入Android的新手来说,可能并没那么好切入,因为我在技术群里看到好多初学者都在问这样的问题。而在Flutter中这将变得轻松自如。

构造函数如下:

   BottomNavigationBar({
    Key key,
    @required this.items,
    this.onTap,
    this.currentIndex = 0,
    this.elevation = 8.0,
    BottomNavigationBarType type,
    Color fixedColor,
    this.backgroundColor,
    this.iconSize = 24.0,
    Color selectedItemColor,
    this.unselectedItemColor,
    this.selectedIconTheme = const IconThemeData(),
    this.unselectedIconTheme = const IconThemeData(),
    this.selectedFontSize = 14.0,
    this.unselectedFontSize = 12.0,
    this.selectedLabelStyle,
    this.unselectedLabelStyle,
    this.showSelectedLabels = true,
    bool showUnselectedLabels,
  })
BottomNavigationBar 常用属性
    items List<BottomNavigationBarItem>        底部导航条按钮集合
    
    iconSize                                   设置icon大小
    
    currentIndex                               默认选中第几个
    
    onTap                                      选中变化回调函数
    
    fixedColor                                 选中的颜色
    
    type                                       BottomNavigationBarType.fixed (固定类型)
    
                                               BottomNavigationBarType.shifting (流动类型)

                                               
    selectedLabelStyle                         选中时文本的样式

    unselectedLabelStyle                       未选中时文本的样式
                                               
    selectedFontSize                           选中时文本的大小            
                                               
    unselectedFontSize                         未选中时文本的大小    

    selectedItemColor                          选择中的颜色和 fixedColor互斥
    
    unselectedItemColor                        未选择中的颜色
    
    showUnselectedLabels                       是否显示未选中的文本标签

BottomNavigationBar的属性都大致了解了,下面我们来结合案例去实践,感受它妖娆的魅力吧O(∩_∩)O

本案例不会再按照以前的写法,把代码都写在main.dart 程序入口中,方便后面的文章写案例和查阅源码学习,在本章开始进行代码抽离。

案例要求: 利用上面的 BottomNavigationBar 组件实现类似微信手机端的底部菜单导航,点击底部导航菜单可以进行页面之间的切换。

首先我们先准备三个页面,主要用于点击底部导航菜单按钮,显示不同的页面效果,内容随便写点,有区分即可:

1、HomePage(主页)
  import 'package:flutter/material.dart';
  
  class HomePage extends StatefulWidget {
    HomePage({Key key}) : super(key: key);
  
    _HomePageState createState() => _HomePageState();
  }
  
  class _HomePageState extends State<HomePage> {
    @override
    Widget build(BuildContext context) {
      return Container(
        color: Colors.cyan,
        child: Center(child: Text("主页"),),
      );
    }
  }
2、CommunityPage(社区)
  import 'package:flutter/material.dart';
  
  class CommunityPage extends StatefulWidget {
    CommunityPage({Key key}) : super(key: key);
  
    _CommunityPageState createState() => _CommunityPageState();
  }
  
  class _CommunityPageState extends State<CommunityPage> {
    @override
    Widget build(BuildContext context) {
      return Container(
        color: Colors.green,
        child: Center(child: Text("社区"),),
      );
    }
  }
3、PersionPage(个人中心)
  import 'package:flutter/material.dart';
  
  class PersionPage extends StatefulWidget {
  
    PersionPage({Key key}) : super(key: key);
  
    _PersionPageState createState() => _PersionPageState();
  }
  
  class _PersionPageState extends State<PersionPage> {
    @override
    Widget build(BuildContext context) {
        return Container(
        color: Colors.blue,
        child: Center(child: Text("个人中心"),),
      );
    }
  }
4、重点来了,新建Tabs.dart文件(名字随意,见名知意即可),按钮的点击有状态的改变,且要反应到视图模板上,这里我们应该使用 StatefulWidget,让Tabs类继承StatefulWidget:
  import 'package:flutter/material.dart';

  //引入准备好的界面
  import 'tabs/CommunityPage.dart';
  import 'tabs/PersionPage.dart';
  import 'tabs/HomePage.dart';

  
  
  class Tabs extends StatefulWidget {

    _TabsState createState() => _TabsState();

  }

  class _TabsState extends State<Tabs> {

    //默认选中的索引
    int _currentIndex=0;

    //将页面封装在集合中,方便通过索引取出
    List _pageList=[
      HomePage(),
      CommunityPage(),
      PersionPage(),
    ];


    @override
    Widget build(BuildContext context) {
      return Scaffold(
          appBar: AppBar(
            title: Text("呆萌"),
          ),
          body: this._pageList[this._currentIndex],

          bottomNavigationBar: BottomNavigationBar(

            //配置选中的索引值
            currentIndex: this._currentIndex,

            onTap: (int index){

              //改变状态
                setState(() {
                    this._currentIndex=index;
                });
            },

            //icon的大小
            iconSize:36.0,

            //选中的颜色
            fixedColor:Colors.red,

            selectedFontSize: 18,

            selectedLabelStyle: TextStyle(color: Colors.red),

            // selectedItemColor: Colors.red, 和 fixedColor互斥

            unselectedFontSize: 18,

            unselectedItemColor:Colors.deepPurple ,

            unselectedLabelStyle: TextStyle(color: Colors.deepPurple),

            showUnselectedLabels: true,

            //配置底部tabs为固定效果,即没有点击后的凸出感
            type:BottomNavigationBarType.fixed,

            // BottomNavigationBarItem 包装的底部按钮
            items: [

              BottomNavigationBarItem(

                icon: Icon(Icons.home),
                title: Text("首页")

              ),
               BottomNavigationBarItem(
                icon: Icon(Icons.question_answer),
                title: Text("社区")
              ),

               BottomNavigationBarItem(
                icon: Icon(Icons.people),
                title: Text("个人中心")
              )
            ],
          ),
        );
    }
  }

Tabs类中并没什么难点,部分也给出了介绍,值得注意的是 这里在点击事件 onTap中我们接收了一个点击后的索引值 并通过调用 setState()方法去改变了当前的索引值和界面的重新 build渲染视图,然后根据变化后的索引值重新在 _pageList 页面集合中取出对应的页面并赋值给 body,然而这就实现了页面切换效果的过程,第四章知识点还记得吗?戳这里回顾 StatefulWidget

5、接着在入口程序中将底部菜单进行引入
  import 'package:flutter/material.dart';
  import 'package:flutter_learn/page/Tabs.dart';


  void main() => runApp(MyApp());

  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
          home:Tabs(), // 返回 Tabs包装的菜单Widget
      );
    }
  }

运行效果见下图,还是挺简单的吧O(∩_∩)O

底部导航.gif

二、AppBar

AppBar中的属性较多,构造函数如下:

   AppBar({
    Key key,
    this.leading,
    this.automaticallyImplyLeading = true,
    this.title,
    this.actions,
    this.flexibleSpace,
    this.bottom,
    this.elevation,
    this.shape,
    this.backgroundColor,
    this.brightness,
    this.iconTheme,
    this.actionsIconTheme,
    this.textTheme,
    this.primary = true,
    this.centerTitle,
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,
    this.toolbarOpacity = 1.0,
    this.bottomOpacity = 1.0,
  })
AppBar 常用属性:
    属性                         描述

    leading                      在标题前面显示的一个控件,在首页通常显示应用
                                 的 logo;在其他界面通常显示为返回按钮
                                 
    title                        标题,通常显示为当前界面的标题文字,可以放组件

    actions                      通常使用 IconButton 来表示,可以放按钮组

    bottom                       通常放 tabBar,标题下面显示一个 Tab 导航栏

    backgroundColor              导航背景颜色

    iconTheme                    图标样式

    textTheme                    文字样式

    centerTitle                  标题是否居中显示

AppBar在之前的案例中一直都有使用只是没有过多使用它的属性而已,现在介绍了,写个简单的页面来练习下。这里先不在上个案例中进行配置,新建一个 AppBarPage界面。

import 'package:flutter/material.dart';

class AppBarPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        //标题
        title: Text("呆萌"),
        //设置Bar背景色
        backgroundColor: Colors.red,
        //标题居中
        centerTitle: true,

        leading: Builder(
          builder: (BuildContext context) {
            return IconButton(

              icon: Icon(Icons.menu),

              onPressed: () {

                Scaffold.of(context)
                    .showSnackBar(SnackBar(content: new Text('点击了左侧按钮')));
              },
            );
          },
        ),
        actions: <Widget>[
          IconButton(

            icon: Icon(Icons.search ,color: Colors.white,),
          ),
          IconButton(

            icon: Icon(Icons.settings,color: Colors.white,),
          )
        ],
      ),
      body: Center(
        child: Text(
          '这是AppBar测试界面',
          style: TextStyle(
            color: Colors.green,
            fontSize: 22,
          ),
        ),
      ),
    );
  }
}

首先是在AppBar左边leading 添加了一个 IconButton 并显示了一个菜单Icon,在点击的时候 弹出个SnackBar提示信息,SnackBar这里不具体讲,这个会在后面章节中详细介绍,需要注意的是当BuildContext在Scaffold之前时,调用Scaffold.of(context)会报错。这时可以通过Builder Widget来解决这个问题。其次通过actions添加了一组图片按钮,而actions接收的是一个List<Widget> 集合,所以这里你可以放多个组件。

接下来配置路由,使得跳转可以到我们刚新建的AppBarPage页面中去。路由还记得吗?戳这里回顾 路由

 //配置路由
  final routes={
     //如有多个,请在这里添加即可。
    '/SecondPage':(context,{arguments})=>SecondPage(arguments:arguments),
    //让AppBarPage 做为launcher界面
    '/':(context)=>AppBarPage(),
  };

为了方便观看 AppBar 中添加的组件,这里使用 debugShowCheckedModeBanner:false把 debug 图标进行去掉。并在入口类中初始路由的时候去加载我们的 AppBarPage 页面

  import 'package:flutter/material.dart';
  import 'route/Routes.dart';


  void main() => runApp(MyApp());

  class MyApp extends StatelessWidget {

    @override
    Widget build(BuildContext context) {
      return MaterialApp(

          //去掉debug图标
          debugShowCheckedModeBanner:false ,

          //初始化的时候加载的路由
          initialRoute: '/',
          onGenerateRoute: onGenerateRoute

      );
    }
  }

运行看看效果吧:


AppBar(eg1).gif

效果还是不错吧,debug 图标也被我们成功的去掉了,这只是模拟器,在真设备上显示效果应该会更好,有没有觉得比原生实现起来简单很多呢?

三、TabBar

在Flutter中可以通过 TabBar 组件可以轻松实现顶部导航条,如不使用 TabBar 组件也是可以,比如通过Row组件去嵌套也是可以的,只是实现起来相对复杂很多。

构造函数如下:

  const TabBar({
    Key key,
    @required this.tabs,
    this.controller,
    this.isScrollable = false,
    this.indicatorColor,
    this.indicatorWeight = 2.0,
    this.indicatorPadding = EdgeInsets.zero,
    this.indicator,
    this.indicatorSize,
    this.labelColor,
    this.labelStyle,
    this.labelPadding,
    this.unselectedLabelColor,
    this.unselectedLabelStyle,
    this.dragStartBehavior = DragStartBehavior.start,
    this.onTap,
  })
TabBar 常用属性:
    属性                      描述

    tabs                      显示的标签内容,一般使用 Tab 对象,也可以是其他的Widget

    controller                TabController 对象

    isScrollable              是否可滚动

    indicatorColor            指示器颜色

    indicatorWeight           指示器高度

    indicatorPadding          底部指示器的 Padding

    indicator                 指示器 decoration,例如边框等

    indicatorSize             指示器大小计算方式,TabBarIndicatorSize.label 跟文字等宽,
                              TabBarIndicatorSize.tab 跟每个 tab 等宽
                              
    labelColor                选中 label 颜色

    labelStyle                选中 label 的 Style

    labelPadding              每个 label 的 padding 值

    unselectedLabelColor      未选中 label 颜色

    unselectedLabelStyle      未选中 label 的 Style

在上面介绍AppBar中有一个bottom属性刚好接收的就是 Widget 组件,TabBar也是一个组件,瞌睡遇到枕头啊!! 下面我们就来创建一个TabBar 组件将其插入。

  import 'package:flutter/material.dart';
  
  class AppBarPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return DefaultTabController(
        //这个一定的和tabs的子元素大小一致
        length: 3,
        
        child: Scaffold(
          appBar: AppBar(
            //标题
            title: Text("呆萌"),
            //设置Bar背景色
            backgroundColor: Colors.red,
            //标题居中
            centerTitle: true,
  
            bottom: TabBar(
  
              //配置指示器颜色
              indicatorColor: Colors.green,
              //让指示器和文字等宽
              indicatorSize: TabBarIndicatorSize.label,
              // 导航Tab
              tabs: <Widget>[
                Tab(text: "视频"),
                Tab(text: "段子"), 
                Tab(text: "趣图")],
            ),
  
            leading: Builder(
              builder: (BuildContext context) {
                return IconButton(
                  icon: Icon(Icons.menu),
                  onPressed: () {
                    Scaffold.of(context)
                        .showSnackBar(SnackBar(content: new Text('点击了左侧按钮')));
                  },
                );
              },
            ),
            actions: <Widget>[
              IconButton(
                icon: Icon(
                  Icons.search,
                  color: Colors.white,
                ),
              ),
              IconButton(
                icon: Icon(
                  Icons.settings,
                  color: Colors.white,
                ),
              )
            ],
          ),
          body: Container(child: Center(child: Text("测试TabBar"),),),
        ),
      );
    }
  }

这段代码中有个非常重要的配置 DefaultTabController 其中 length的大小一定等于 TabBar 中 tabs的子元素数量,否则报错。而 tabs 接收的是一个List<Widget> 集合,在实际开发中,length往往需要动态配置。

TabBar(eg1).gif

可以看到我们实现了顶部菜单导航条,你可能马上会问,顶部导航是实现了,但我想点击顶部的菜单按钮后让下面空白区域跟随着进行页面联动,那该怎样实现呢? 想想下,Flutter中一个页面也是Widget,那我能不能给每个导航按钮绑定一个页面呢? 点击每个按钮就显示绑定的页面Widget即可!!

嘿嘿,确实是可以的,Flutter中也是这么做的并提供了 TabBarView 组件让我们去实现。

TabBarView构造函数:

  const TabBarView({
    Key key,
    @required this.children,
    this.controller,
    this.physics,
    this.dragStartBehavior = DragStartBehavior.start,
  }) 

TabBarView中 children 属性接收的也是一个List<Widget> 那便不难看出:TabBar中的 tabs 的子元素和 TabBarView 中的子元素通过下标进行了一 一绑定。也就是说导航菜单有多少个按钮,TabBarView中就要准备多少个页面组件。基于这个原理我们把上面案例中的 body 替换成如下

   body: TabBarView(

      children: <Widget>[
        Container(
          child: Center(
            child: Text("视频专区"),
          ),
        ),
        Container(
          child: Center(
            child: Text("段子专区"),
          ),
        ),
        Container(
          child: Center(
            child: Text("趣图专区"),
          ),
        )
       
      ],
    ),

运行试看下效果:

TabBarView.gif

果不其然,确实这样就实现了联动。是不是很激动?下面我来看一种情况:我们将顶部导航菜单增加到 7 个 甚至更多看看是个什么效果,添加之后注意修改 length 的长度 :

         // 导航Tab
          tabs: <Widget>[
            Tab(text: "视频"),
            Tab(text: "段子"),
            Tab(text: "趣图"),

            Tab(text: "测试"),
            Tab(text: "测试"),
            Tab(text: "测试"),

            Tab(text: "测试"),
          ],
TabBarView(eg2).gif

尼玛啊! 菜单被逐渐覆盖了,也就是说当前一屏的宽度不能够足以均分给这么多个导航菜单,导致了按钮不能完全显示出来!!那能不能让它能够自适应,如果有很多菜单,就让它能左右滑动呢? 当然可以 上面TabBar属性介绍中 isScrollable 就能让 TabBar 实现可以左右滑动。下面添加一个属性试试。

  import 'package:flutter/material.dart';
  
  class AppBarPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        //去掉debug图标
        debugShowCheckedModeBanner:false ,
  
        home: DefaultTabController(
          //这个一定的和tabs的子元素大小一致
          length: 7,
  
          child: Scaffold(
            appBar: AppBar(
              //标题
              title: Text("呆萌"),
              //设置Bar背景色
              backgroundColor: Colors.red,
              //标题居中
              centerTitle: true,
  
              bottom:  TabBar(
  
                //设置选中菜单文字颜色
                labelColor: Colors.white,
                //未选中文字菜单的颜色
                unselectedLabelColor: Colors.deepPurple,
  
  
                isScrollable: true,
                //配置指示器颜色
                indicatorColor: Colors.green,
                //让指示器和文字等宽
                indicatorSize: TabBarIndicatorSize.label,
                // 导航Tab
                tabs: <Widget>[
                  Tab(text: "视频"),
                  Tab(text: "段子"),
                  Tab(text: "趣图"),
                  Tab(text: "测试"),
                  Tab(text: "测试"),
                  Tab(text: "测试"),
                  Tab(text: "测试"),
                ],
              ),
  
  
              leading: Builder(
                builder: (BuildContext context) {
                  return IconButton(
                    icon: Icon(Icons.menu),
                    onPressed: () {
                      Scaffold.of(context)
                          .showSnackBar(SnackBar(content: new Text('点击了左侧按钮')));
                    },
                  );
                },
              ),
              actions: <Widget>[
                IconButton(
                  icon: Icon(
                    Icons.search,
                    color: Colors.white,
                  ),
                ),
                IconButton(
                  icon: Icon(
                    Icons.settings,
                    color: Colors.white,
                  ),
                )
              ],
            ),
            body: TabBarView(
              children: <Widget>[
                Container(
                  child: Center(
                    child: Text("视频专区"),
                  ),
                ),
                Container(
                  child: Center(
                    child: Text("段子专区"),
                  ),
                ),
                Container(
                  child: Center(
                    child: Text("趣图专区"),
                  ),
                ),
                Container(
                  child: Center(
                    child: Text("趣图专区"),
                  ),
                ),
                Container(
                  child: Center(
                    child: Text("趣图专区"),
                  ),
                ),
                Container(
                  child: Center(
                    child: Text("趣图专区"),
                  ),
                ),
                Container(
                  child: Center(
                    child: Text("趣图专区"),
                  ),
                ),
              ],
            ),
          ),
        ),
      );
    }
  }

效果如下:

TabBarView(eg3).gif

特别需要注意的是在不知道顶部菜单具体有多少个的时候(尤其是菜单是根据服务器获取的情况)或者顶部有很多个菜单需要放置时一定得加上 isScrollable 属性。

此时你可能会说,感觉这样的方式实现好复杂,有没有像底部导航菜单那样更简单高效的实现方案啊? 当然是有的,请继续往下看。

三、TabController

上面的例子中 DefaultTabController 组件实现了AppBar里面的顶部导航切换,但是在项目中有数据请求,上拉加载更多等操作的时候,或者想在统一回调函数中处理点击菜单事件的监听,那么前面的写法,就不是很方便操作,因此,在flutter里面,还提供了一个用于实现顶部导航的组件 TabController

新建一个TabBarController.dart 页面 ,并继承 StatefulWidget 的有状态组件,并且实现 SingleTickerProviderStateMixin 这个类,这里用到了Dart 中的 mixin。你可以简单的理解就是通过 with 实现了多继承。

  import 'package:flutter/material.dart';
  
  class TabBarControllerPage extends StatefulWidget {
    TabBarControllerPage({Key key}) : super(key: key);
  
    _TabBarControllerPageState createState() => _TabBarControllerPageState();
  }
  
  class _TabBarControllerPageState extends State<TabBarControllerPage>
      with SingleTickerProviderStateMixin {
  
    TabController _tabController;
  
    @override
    void dispose() {
      super.dispose();
      //在组件树中移除时销毁_tabController
      _tabController.dispose();
    }
  
    @override
    void initState() {
      super.initState();
      
      _tabController = new TabController(vsync: this, length: 4);
  
      //监听点击事件的回调
      _tabController.addListener(() {
  
        print( getView()[_tabController.index].key.toString());
  
      });
    }
  
    List<Widget> getView() {
  
      List<Widget> viewWidget = new List();
  
      //配置下key 方便日子看出 是否回调了 对应的菜单
      Tab tab = Tab(key: Key("video"), text: "视频");
      Tab tab1 = Tab(key: Key("Duan Zi"), text: "段子");
      Tab tab2 = Tab(key: Key("Pic"), text: "趣图");
      Tab tab3 = Tab(key: Key("Test"), text: "测试");
  
      viewWidget.add(tab);
      viewWidget.add(tab1);
      viewWidget.add(tab2);
      viewWidget.add(tab3);
  
      return viewWidget;
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text("呆萌"),
          bottom: TabBar(
  
              //根据你的场景进行配置
              // isScrollable: true,
  
              //指示器颜色
              indicatorColor: Colors.red,
  
              //设置选中菜单文字颜色
              labelColor: Colors.white,
              //未选中文字菜单的颜色
              unselectedLabelColor: Colors.yellowAccent,
  
              //使得 TabBar 和 TabBarView之间进行关联,需要同时配置该对象 _tabController
              controller: this._tabController,
              tabs: getView()),
        ),
        body: TabBarView(
  
          controller: this._tabController,
          children: <Widget>[
            Container(
              child: Center(
                child: Text("视频专区"),
              ),
            ),
            Container(
              child: Center(
                child: Text("段子专区"),
              ),
            ),
            Container(
              child: Center(
                child: Text("趣图专区"),
              ),
            ),
            Container(
              child: Center(
                child: Text("测试专区"),
              ),
            ),
          ],
        ),
      );
    }
  }

特别需要注意的是 在 TabController 依然是需要指定 tabs的长度,且 TabBarTabBarView 产生关联关系都是通过 controller 属性进行配置。

重新配置初始化路由到 TabBarControllerPage 界面,下面我们来运行看下效果。

TabController方案.gif

点击切换菜单按钮后控制台输出结果:

    I/flutter ( 2731): [<'Duan Zi'>]
    I/flutter ( 2731): [<'Duan Zi'>]
    I/flutter ( 2731): [<'Pic'>]
    I/flutter ( 2731): [<'Pic'>]
    I/flutter ( 2731): [<'Test'>]
    I/flutter ( 2731): [<'Test'>]
    I/flutter ( 2731): [<'video'>]
    I/flutter ( 2731): [<'video'>]
    I/flutter ( 2731): [<'Duan Zi'>]
    I/flutter ( 2731): [<'Duan Zi'>]
    I/flutter ( 2731): [<'video'>]

在回调中确实能获取到点击菜单按钮的后的下标,并通过下标取出了对应页面的Key值信息。在案例中明显可以看出 通过 TabController 方式去实现顶部导航菜单会更加简洁且灵活,因此开发中这样的需求我们应该多使用这种方式去实现。

好了本章节就此结束,又到了说再见的时候了,如果你喜欢请留下你的小红星,你们的支持才是创作的动力,如有错误,请热心的你留言指正, 谢谢大家观看,下章再会 O(∩_∩)O

实例源码地址: https://github.com/zhengzaihong/flutter_learn

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

推荐阅读更多精彩内容