Flutter - 底部导航栏解析与示范
1. BottomNavigationBar 详解
显示在应用程序底部的 material Widget,用于在少量视图中进行选择,通常在 3 - 5 个之间。底部导航栏由文本标签、图标或两者形式的多个项目组成,布置在一块 material 的顶部。它在应用程序的顶级视图之间提供快速导航。对于更大的屏幕比如10.6存及以上大小的平板,侧边导航比底部导航栏可能更合适。底部导航栏通常与Scaffold
一起使用,它作为Scaffold.bottomNavigationBar
参数提供。
底部导航栏的类型会更改其项目的显示方式。如果未指定,则在少于 4 个项目时,自动设置为BottomNavigationBarType.fixed
,否则设置为BottomNavigationBarType.shifting
。
项目的长度必须至少为两个,并且每个项目的图标和标题/标签不得为空。
1.1 BottomNavigationBar 类
的构造函数
BottomNavigationBar({ Key? key, // Widget、Element 和 SemanticsNode 的标识符。 required List<BottomNavigationBarItem> items, // 带有图标和标题的按钮的列表,见 1.2 节 ValueChanged<int>? onTap, int currentIndex = 0, // 当前活动的BottomNavigationBarItem 的项目索引。 double? elevation, // 用于定义该 BottomNavigationBar 相对于其父级的`z`轴坐标,如果为 null,则默认为8.0。 BottomNavigationBarType? type, // 定义 BottomNavigationBar 的布局和行为。见 1.3 节 Color? fixedColor, // 颜色,为ARGB 格式的不可变 32 位颜色值。 Color? backgroundColor, // 背景填充色,为ARGB 格式的不可变 32 位颜色值。 double iconSize = 24.0, // 所有 BottomNavigationBarItem 图标的大小。[...] Color? selectedItemColor, // 选定的 BottomNavigationBarItem.icon 和 BottomNavigationBarItem.title 的颜色。[...] Color? unselectedItemColor, IconThemeData? selectedIconTheme, // 当前选中的BottomNavigationBarItem.icon 中图标的大小、不透明度和颜色 。[...] IconThemeData? unselectedIconTheme, // 当前未选中的BottomNavigationBarItem.icon 中图标的大小、不透明度和颜色 。[...] double selectedFontSize = 14.0, // BottomNavigationBarItem标签被选中时 的字体大小。[...] double unselectedFontSize = 12.0, TextStyle? selectedLabelStyle, TextStyle? unselectedLabelStyle, // 未选中时 ,BottomNavigationBarItem标签 的TextStyle 。 bool? showSelectedLabels, // 是否为选定的BottomNavigationBarItem显示标签。 bool? showUnselectedLabels, // 是否为未选中的BottomNavigationBarItem显示标签。 MouseCursor? mouseCursor, bool? enableFeedback, BottomNavigationBarLandscapeLayout? landscapeLayout })
其中:
创建底部导航栏,通常用作 Scaffold
的 Scaffold.bottomNavigationBar
参数。items
的长度必须至少为两个,并且每个项目的图标和标签不能为空。
- 如果 type为 null ,则当有两个或三个item时使用
BottomNavigationBarType.fixed
,否则使用BottomNavigationBarType.shifting
。 - iconSize 、selectedFontSize、unselectedFontSize和 elevation 参数必须为非空且非负数。
- 如果
selectedLabelStyle.color
和unselectedLabelStyle.color
值为非空,将使用它们来代替selectedItemColor
和unselectedItemColor
。 - 如果使用自定义
IconThemeDatas
,您必须同时提供selectedIconTheme
和unselectedIconTheme
,并且必须同时设置IconThemeData.color
和IconThemeData.size
。 - 如果同时设置了
selectedLabelStyle.fontSize
和selectedFontSize
,将使用selectedLabelStyle.fontSize
。 - 只能指定selectedItemColor和fixedColor之一。前者是首选,fixedColor的存在只是为了向后兼容。
- 如果
showSelectedLabels
为null
,则使用bottonnavigationbarthemedata.showselectedlables
。如果bottonnavigationbarthemdata.showSelectedLabels
为null
,则showSelectedLabels
默认为true
。 - 如果
showUnselectedLabels
为空,则使用bottonnavigationbarthemedata.showunselectedlables
。如果bottonnavigationbarthedata.showselectedlabels
为空,则当类型为BottomNavigationBarType.fixed
时,showUnselectedLabels
默认为true
,当类型为BottomNavigationBarType.shifting
时,showUnselectedLabels
默认为false
。
1.2 关于BottomNavigationBarItem
类 (items 列表 成员的类型)
这个类很少单独使用。它通常嵌入在上面的底部导航小部件之一中。它一般是 material 的 BottomNavigationBar
或带有图标和标题 的 iOS 主题 CupertinoTabBar
中的交互式按钮。
其构造函数:
const BottomNavigationBarItem({ required Widget icon, @Deprecated('Use "label" instead, as it allows for an improved text-scaling experience. ' 'This feature was deprecated after v1.19.0.') Widget? title, String? label, Widget? activeIcon, Color? backgroundColor, String? tooltip })
1.2.1 属性
名称 | 描述1 | 描述2 | 描述3 |
activeIcon |
Widget |
final |
选择此底部导航项时显示的替代图标。[…] |
backgroundColor |
Color ? |
final | material BottomNavigationBar 的背景径向动画的颜色。[…] |
hashCode |
int |
read-only , inherited |
此对象的哈希码。[…] |
icon |
Widget |
final | 项目的图标。[…] |
label |
String ? |
final | BottomNavigationBarItem 的文本标签。[…] |
runtimeType |
Type |
read-only , inherited |
对象的运行时类型的表示。 |
title |
Widget ? |
final |
项目的标题。如果未提供标题,则只有在 Material Design BottomNavigationBar中未使用时才会显示该图标。[…]@Deprecated (‘使用 “label” 代替,因为它可以改善文本缩放体验。’ ‘此功能在 v1.19.0 之后被弃用。’) |
rtooltip |
String? | final | 当用户长按该项目时 ,此 BottomNavigationBarItem 的工具提示中显示的文本。[…] |
1.2.2 方法
1. (inherited) noSuchMethod()
noSuchMethod(Invocation invocation) → dynamic
在访问不存在的方法或属性时调用。[…]
2. (inherited) toString()
toString() → String
此对象的字符串表示形式。[…]
1.2.3 运算符
1. (inherited) ==
operator ==(Object other) → bool
相等运算符。[…]
1.3 BottomNavigationBarType 枚举
常量
1. fixed
fixed → const BottomNavigationBarType
底部导航栏的底部导航栏具有固定宽度
。如果固定则主要是体现在当点击某个导航按钮时,被点击的按钮不会移动也不会改变大小。
const BottomNavigationBarType(0)
2. shifting
shifting → const BottomNavigationBarType
底部导航栏底部导航栏的位置和大小会产生动画,当轻击标签时,标签会淡入。
const BottomNavigationBarType(1)
3. values
values → const List<BottomNavigationBarType>
此枚举中值的常量列表,按其声明顺序排列。
2. BottomNavigationBar 示范项目
2.1 项目结构
2.2 代码
项目入口:main.dart
import 'package:flutter/material.dart'; import 'tabsPage/index.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( // title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. primarySwatch: Colors.orange, ), home: const Tabs()); } }
底部导航控制:tabsPage/index.dart
import 'package:flutter/material.dart'; import 'Page1.dart'; import 'Page2.dart'; import 'Page3.dart'; import 'Page4.dart'; class Tabs extends StatefulWidget { const Tabs({Key? key}) : super(key: key); @override _TabsState createState() => _TabsState(); } class _TabsState extends State<Tabs> { int _selectedIndex = 0; // 用作被选中的 Tab 的索引号 final List _tabPages = [ const Page1(), const Page2(), const Page3(), const Page4() ]; // 列举所有 Tab 控制切换将用到的页面 @override Widget build(BuildContext context) { return Scaffold( body: _tabPages[_selectedIndex], bottomNavigationBar: BottomNavigationBar( selectedFontSize: 12.0, // 被选中时的字体大小 unselectedFontSize: 14.0, // 未被选中时的字体大小 showSelectedLabels: true, // 被选中时是否显示Label showUnselectedLabels: true, // 未被选中时是否显示Label enableFeedback: true, //点击会产生咔嗒声,长按会产生短暂的振动 selectedItemColor: Colors.orange, // 设置被选中时的图标颜色 unselectedItemColor: Colors.grey, // 设置未被选中时的图标颜色 items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon( Icons.home, size: 24.0, ), label: '工作室', backgroundColor: Colors.white, ), BottomNavigationBarItem( icon: Icon(Icons.event_note, size: 24.0), label: '数据', backgroundColor: Colors.white, ), BottomNavigationBarItem( icon: Icon(Icons.account_box_outlined, size: 24.0), label: '通讯录', backgroundColor: Colors.white, ), BottomNavigationBarItem( icon: Icon(Icons.person, size: 24.0), label: '我的', backgroundColor: Colors.white, ), ], // 设置当前(即被选中时)页面 currentIndex: _selectedIndex, // 当点击其中一个[items]被触发 onTap: (int index) { setState(() { /* * item 被点中时更改当前索引。 * 其中,currentIndex 字段设置的值时响应式的 * 新版dart不用this. */ _selectedIndex = index; }); }, ), ); } }
底部导航得具体页面:
page1
import 'package:flutter/material.dart'; enum WhyFarther { harder, smarter, selfStarter, tradingCharter } class Page1 extends StatefulWidget { const Page1({Key? key}) : super(key: key); @override _Page1State createState() => _Page1State(); } class _Page1State extends State<Page1> { set _selection(WhyFarther _selection) {} @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("JcStdio"), actions: <Widget>[ IconButton( icon: const Icon(Icons.search), onPressed: () {}, ), PopupMenuButton<WhyFarther>( onSelected: (WhyFarther result) { setState(() { _selection = result; }); }, icon: const Icon( Icons.add_circle_outline, size: 24.0, ), itemBuilder: (BuildContext context) => <PopupMenuEntry<WhyFarther>>[ PopupMenuItem<WhyFarther>( value: WhyFarther.selfStarter, child: Row( children: const [ Icon( Icons.qr_code_scanner_outlined, size: 24.0, ), Text('扫一扫') ], )), PopupMenuItem<WhyFarther>( value: WhyFarther.tradingCharter, child: Row( children: const [ Icon( Icons.person_add, size: 24.0, ), Text('添加朋友') ], )), ], ) ]), body: const Text('这里是 jcstdio: jclee95的个人工作室,欢迎你的到来!'), ); } }
page2
import 'package:flutter/material.dart'; class Page2 extends StatefulWidget { const Page2({Key? key}) : super(key: key); @override _Page2State createState() => _Page2State(); } class _Page2State extends State<Page2> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("数据")), body: const Text('欢迎浏览数据中心页...'), ); } }
page3
import 'package:flutter/material.dart'; import '/datas/listData.dart'; // import '/datas/usercard.dart'; class Page3 extends StatefulWidget { const Page3({Key? key}) : super(key: key); @override _Page3State createState() => _Page3State(); } class _Page3State extends State<Page3> { List<Widget> _listData() { var tempList = listData.map((value) { return ListTile( leading: Image.network(value["imageurl"]), title: Text(value["title"]), subtitle: Text(value["author"])); }); return tempList.toList(); } // UserCard @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("通讯录"), ), body: ListView( children: _listData(), )); } }
page4
import 'package:flutter/material.dart'; class Page4 extends StatefulWidget { const Page4({Key? key}) : super(key: key); @override _Page3State createState() => _Page3State(); } class _Page3State extends State<Page4> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("我的"), ), body: const Text('欢迎来到个人中心!')); } }
3. 项目效果
3.1 page1
3.2 page2
3.3 page3
3.4. page4