ForgetSou | Blog

❤ 武统台湾 刻不容缓 ❤

0%

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. 外弧效果

    底部导航外弧显示

在凸出效果代码基础上做修改,打开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


-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道