简介
底部导航栏是我们日常开发中经常用到的导航工具,用于切换到不同的展示页,比如微信、支付宝、淘宝等大厂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
-
导入模块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';
-
添加底部导航按钮及点击控制
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,
),
);
}
}
特殊效果
- 凸出效果
在我们平时开发过程中不一定都是这种规则的底部导航,有时需要我们做出一些特殊处理,比如中间的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,
);
}
- 外弧效果
在凸出效果代码基础上做修改,打开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
个人博客: 🏡 ForgetSou