Flutter-创建底部导航栏

简介

底部导航栏是我们日常开发中经常用到的导航工具,用于切换到不同的展示页,比如微信、支付宝、淘宝等大厂APP都是使用底部导航栏设计,此设计也符合用户的使用习惯,下面我们使用flutter创建一个简单的底部工具栏。

效果图

示例

在fluuter开发中,万物皆是Wdiget, flutter官方提供的一个底部导航组件BottomNavigationBar,我们就使用BottomNavigationBar创建。

方式一

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show SynchronousFuture;
import 'package:flutter_localizations/flutter_localizations.dart';

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

class ZDLocalizations {
  ZDLocalizations(this.locale);

  final Locale locale;

  static ZDLocalizations of(BuildContext context) {
    return Localizations.of<ZDLocalizations>(context, ZDLocalizations);
  }

  static Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'title': 'List View',
    },
    'zh': {
      'title': '列表视图',
    },
  };

  String get title {
    return _localizedValues[locale.languageCode]['title'];
  }
}

class DemoLocalizationsDelegate extends LocalizationsDelegate<ZDLocalizations> {
  const DemoLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);

  @override
  Future<ZDLocalizations> load(Locale locale) {
    // Returning a SynchronousFuture here because an async "load" operation
    // isn't needed to produce an instance of DemoLocalizations.
    return SynchronousFuture<ZDLocalizations>(ZDLocalizations(locale));
  }

  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
}

// 静态类
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // final wordPair = new WordPair.random();
    return new MaterialApp(
      debugShowCheckedModeBanner: false,
      home: new CreateHome(),
      theme: new ThemeData(
        primaryColor: Colors.orange,
      ),
      // 国际化
      localizationsDelegates: [
        const DemoLocalizationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: [const Locale('en', ''), const Locale('zh', '')],
    );
  }
}

// 动态类
class CreateHome extends StatefulWidget {
  @override
  CreateHomeState createState() => CreateHomeState();
}

class CreateHomeState extends State<CreateHome> {
  int _currentIndex = 0;
  final _bodyOptions = [
    Text('主页'),
    Text('商城'),
    Text('消息'),
  ];

  // IBAction
  void backOnPressed() {}

  void menuOnPressed() {}

  void onTabBarItemTapped(int idx) {
    setState(() {
      _currentIndex = idx;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _createAppBar(),
      body: Center(child: _bodyOptions.elementAt(_currentIndex)),
      bottomNavigationBar: _createTabBar(),
    );
  }

  Widget _createAppBar() {
    return new AppBar(
      brightness: Brightness.dark,
      elevation: 0.5,
      iconTheme: IconThemeData(color: Colors.white),
      title: Text(
        ZDLocalizations.of(context).title,
        style: TextStyle(color: Colors.white),
      ),
      actions: [
        IconButton(
          icon: Icon(Icons.menu),
          onPressed: menuOnPressed,
        ),
      ],
      leading: IconButton(
        icon: Icon(
          Icons.arrow_back_ios,
        ),
        onPressed: backOnPressed,
      ),
    );
  }

  Widget _createTabBar() {
    return new BottomNavigationBar(
      fixedColor: Colors.blue,
      backgroundColor: Colors.orange,
      currentIndex: _currentIndex,
      onTap: onTabBarItemTapped,
      items: [
        BottomNavigationBarItem(icon: Icon(Icons.home), label: '主页'),
        BottomNavigationBarItem(icon: Icon(Icons.shop), label: '商城'),
        BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息')
      ],
    );
  }
}

说明:

以上代码有国际化示例部分,本博主比较懒,国际化的内容没删除,直接从工程里Copy出来的。

方式二

创建模块

模块划分

home_page 首页
dialogue_page 对话
record_page 录音
mine_page 我的

home_page.dart内添加模块组件

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      margin: EdgeInsets.only(top: 0, left: 0, bottom: 0, right: 0),
      color: Colors.white,
    ));
  }
}

其他模块同上,具体模块的Scaffold内容根据具体业务来实现,本文直接复制home_page.dart的内容,略微修改Container的颜色以区分模块。

创建组件控制dart

  1. 导入模块dart

    import 'package:flutter/material.dart';
    import 'pages/home_page.dart';
    import 'pages/dialogue_page.dart';
    import 'pages/record_page.dart';
    import 'pages/mine_page.dart';
    
  2. 添加底部导航按钮及点击控制

    class IndexPage extends StatefulWidget {
      @override
      _IndexPageState createState() => _IndexPageState();
    }
    
    class _IndexPageState extends State<IndexPage> {
      // 底部导航按钮数组
      final List<BottomNavigationBarItem> bottomTabs = [
        BottomNavigationBarItem(
             // 使用asset图片,也可以使用系统提供的图片,如Icons.home等
            icon: Image.asset(
              'images/home_unselected.png',
              width: 18,
              height: 18,
            ),
            activeIcon: Image.asset(
              'images/home_selected.png',
              width: 18,
              height: 18,
            ),
            label: '首页'),
        BottomNavigationBarItem(
            icon: Image.asset(
              'images/dialogue_unselected.png',
              width: 18,
              height: 18,
            ),
            activeIcon: Image.asset(
              'images/dialogue_selected.png',
              width: 18,
              height: 18,
            ),
            label: '对话翻译'),
        BottomNavigationBarItem(
            icon: Image.asset(
              'images/record_unselected.png',
              width: 18,
              height: 18,
            ),
            activeIcon: Image.asset(
              'images/record_selected.png',
              width: 18,
              height: 18,
            ),
            label: '录音翻译'),
        BottomNavigationBarItem(
            icon: Image.asset(
              'images/mine_unselected.png',
              width: 18,
              height: 18,
            ),
            activeIcon: Image.asset(
              'images/mine_selected.png',
              width: 18,
              height: 18,
            ),
            label: '我的')
      ];
      // 模块容器数组
      final List tabPages = [HomePage(), DialoguePage(), RecordPage(), MinePage()];
      // 当前选择index
      int currentIndex = 0;
      // 当前页
      var currentPage;
    
      @override
      void initState() {
        currentPage = tabPages[currentIndex];
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: currentIndex,
            backgroundColor: Colors.lightGreen,
            type: BottomNavigationBarType.fixed,
            // elevation: 0,
            items: bottomTabs,
            onTap: (idx) {
              // 必须实现setState方法
              setState(() {
                currentIndex = idx;
                currentPage = tabPages[idx];
              });
            },
          ),
          appBar: AppBar(
            backgroundColor: Colors.lightGreen,
            title: Text(
              bottomTabs[currentIndex].label,
              style: TextStyle(color: Colors.orange),
            ),
            elevation: .5,
          ),
          body: currentPage,
        );
      }
    }
    

实现main.dart

import 'package:flutter/material.dart';
// 1.导入index_page.dart
import './index_page.dart';

// 2. 调用runApp
void main() => runApp(new MyApp());

// 3. 实现静态组件
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: false,
      // 4. 调用IndexPage()方法,填充组件
      home: IndexPage(),
      theme: new ThemeData(
        primaryColor: Colors.orange,
      ),
    );
  }
}

特殊效果

  1. 凸出效果

在我们平时开发过程中不一定都是这种规则的底部导航,有时需要我们做出一些特殊处理,比如中间的item单元凸出显示。

底部导航Item单元凸出显示

在这里我们可以通过Scaffold中的floatingActionButton属性来实现这个效果

@override
Widget build(BuildContext context) {
  return Scaffold(
    bottomNavigationBar: BottomNavigationBar(
      currentIndex: currentIndex,
      backgroundColor: Colors.white,
      type: BottomNavigationBarType.fixed,
      // elevation: 0,
      items: bottomTabs,
      onTap: (idx) {
        setState(() {
          currentIndex = idx;
          currentPage = tabPages[idx];
        });
      },
    ),
    floatingActionButton: FloatingActionButton(
      child: Icon(
        Icons.add,
        size: 30,
      ),
      onPressed: () {
        setState(() {
          currentIndex = 2;
          currentPage = tabPages[2];
        });
      },
      backgroundColor: Colors.orange,
      foregroundColor: Colors.black,
      elevation: 5,
    ),
    floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    appBar: AppBar(
      backgroundColor: Colors.lightGreen,
      title: Text(
        bottomTabs[currentIndex].label,
        style: TextStyle(color: Colors.orange),
      ),
      elevation: 0.5,
    ),
    body: currentPage,
  );
}
  1. 外弧效果

出处:路饭网
flutter BottomAppBar实现不规则底部导航栏

底部导航外弧显示

在凸出效果代码基础上做修改,打开index_page.dart,修改为以下代码
注释掉凸出效果的State代码

class _OuterArcState extends State<IndexPage> {
  @override
  void initState() {
    ///初始化,这个函数在生命周期中只调用一次
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    //构建页面
    return buildBottomTabScaffold();
  }

  //当前显示页面的
  int currentIndex = 0;
  //点击导航项是要显示的页面
  final pages = [HomePage(), DialoguePage(), RecordPage(), MinePage()];

  Widget buildBottomTabScaffold() {
    return SizedBox(
        height: 100,
        child: Scaffold(
          //对应的页面
          body: pages[currentIndex],
          //appBar: AppBar(title: const Text('Bottom App Bar')),
          //悬浮按钮的位置
          floatingActionButtonLocation:
              FloatingActionButtonLocation.centerDocked,
          //悬浮按钮
          floatingActionButton: FloatingActionButton(
            child: const Icon(Icons.add),
            onPressed: () {},
          ),
          //其他菜单栏
          bottomNavigationBar: BottomAppBar(
            shape: CircularNotchedRectangle(),
            notchMargin: 6.0,
            color: Colors.white,
            child: Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                buildBotomItem(currentIndex, 0, Icons.home, "首页"),
                buildBotomItem(currentIndex, 1, Icons.chat, "对话"),
                buildBotomItem(currentIndex, -1, null, "商城"),
                buildBotomItem(currentIndex, 2, Icons.mic, "录音"),
                buildBotomItem(currentIndex, 3, Icons.person, "我的"),
              ],
            ),
          ),
        ));
  }

  buildBotomItem(int selectIndex, int index, IconData iconData, String title) {
    //未选中状态的样式
    TextStyle textStyle = TextStyle(fontSize: 12.0, color: Colors.grey);
    MaterialColor iconColor = Colors.grey;
    double iconSize = 20;
    EdgeInsetsGeometry padding = EdgeInsets.only(top: 8.0);

    if (selectIndex == index) {
      //选中状态的文字样式
      textStyle = TextStyle(fontSize: 13.0, color: Colors.blue);
      //选中状态的按钮样式
      iconColor = Colors.blue;
      iconSize = 25;
      padding = EdgeInsets.only(top: 6.0);
    }
    Widget padItem = SizedBox();
    if (iconData != null) {
      padItem = Padding(
        padding: padding,
        child: Container(
          color: Colors.white,
          child: Center(
            child: Column(
              children: <Widget>[
                Icon(
                  iconData,
                  color: iconColor,
                  size: iconSize,
                ),
                Text(
                  title,
                  style: textStyle,
                )
              ],
            ),
          ),
        ),
      );
    }
    Widget item = Expanded(
      flex: 1,
      child: new GestureDetector(
        onTap: () {
          if (index != currentIndex) {
            setState(() {
              currentIndex = index;
            });
          }
        },
        child: SizedBox(
          height: 52,
          child: padItem,
        ),
      ),
    );
    return item;
  }
}

Demo

GitHub ~ FSFlutterDemo


个人博客: 🏡 ForgetSou


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

推荐阅读更多精彩内容