简介
本文介绍实现底部导航栏的几种方式和注意事项。
1、使用BottomNavigationBar
1.1 效果图
1.2 代码示例
class MainPage extends StatefulWidget {
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentPage = 0;//当前页面
//导航栏每个图标对应的页面
List<Widget> _pages = <Widget>[ HomePage(), SocialPage(), KnowledgePage(), ShoppingPage() ];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
children: _pages,
index: _currentPage,
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
//设置导航栏的图标及文字
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首页')),
BottomNavigationBarItem(icon: Icon(Icons.chat), title: Text('好友')),
BottomNavigationBarItem(icon: Icon(Icons.description), title: Text('文章')),
BottomNavigationBarItem(icon: Icon(Icons.shop), title: Text('商城')),
],
currentIndex: _currentPage,
selectedItemColor: Colors.redAccent,//被选中的图标和文字颜色
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
unselectedItemColor: Colors.black45,//未选中的图标和文字颜色
onTap: _onPageChanged,//点击事件
),
);
}
void _onPageChanged(int index) {
setState(() {
//通过setState切换页面
_currentPage = index;
});
}
1.3 实现像闲鱼中间按钮凸起的效果
- 实现方式:使用floatingActionButton组件,位置放在底部中间,_pages的第三个位置加一个Container()占位,同时BottomNavigationBar的items第三个位置不添加icon等,代码如下:
List<Widget> _pages = <Widget>[
HomePage(),
SocialPage(),
Container(), //占位
KnowledgePage(),
ShoppingPage()
];
//设置floatingActionButton加号
floatingActionButton: GestureDetector(
behavior: HitTestBehavior.opaque,
child: Container(
margin: EdgeInsets.only(top: 25.0),
child: Image(
image: AssetImage('images/add.png'),
height: 67.0,
width: 67.0,
fit: BoxFit.cover,
),
),
onTap: () {
//点击事件
},
),
///BottomNavigationBar
items: const <BottomNavigationBarItem>[
//设置导航栏的图标及文字
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首页')),
BottomNavigationBarItem(icon: Icon(Icons.chat), title: Text('好友')),
//占位
BottomNavigationBarItem(
icon: Container(height: 30.0,width: 80.0,),
activeIcon: Container(height: 30.0,width: 80.0,),
label: "",
)
BottomNavigationBarItem(icon: Icon(Icons.description), title: Text('文章')),
BottomNavigationBarItem(icon: Icon(Icons.shop), title: Text('商城')),
]
1.4 注意事项
- body用IndexedStack,避免切换底部标签界面重绘;
- BottomNavigationBar的showUnselectedLabels属性要设置为true,否则未选中项只显示图片,不显示文字;
- BottomNavigationBarType有两个值,shifting:切换时对应标签会有偏移动画,fixed:切换时无动画效果,一般用fixed;
1.5 使用PageView代替IndexedStack
- 使用IndexedStack的弊端:每次打开app,对应children的各个页面都会加载,就会造成不必要的资源浪费,可以使用PageView实现点击某个导航栏再加载对应页面。
//使用PageView和AutomaticKeepAliveClientMixin避免切换底部tab页面重绘
body: PageView.builder(
controller: _pageController,
itemCount: _pages.length,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return _pages[index];
})
//导航栏对应页面要混入AutomaticKeepAliveClientMixin
class HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {}
//build方法要加super.build(context);
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold();
}
//实现AutomaticKeepAliveClientMixin的方法并返回true
@override
bool get wantKeepAlive => true;
2、使用BottomAppBar
- BottomAppBar可以实现不规则底部导航栏
2.1 示例图
实现中间按钮凸起效果:
2.2 示例代码
- bottomNavigationBar使用BottomAppBar
- CustomShape定义了底部导航栏的形状
- _bottomBar()为两侧按钮布局
bottomNavigationBar: BottomAppBar(
color: Colors.white,
elevation: 10,
shape: CustomShape(),
child: SizedBox(height: 50, child: _bottomBar()),
)),
- 如上图,按钮上方弧线是使用3个二阶贝塞尔曲线连接起来的
class CustomShape extends NotchedShape {
@override
Path getOuterPath(Rect host, Rect? guest) {
Path path = Path();
var pLeft1 = Offset(host.width / 2 - 45, host.top);
var pLeft2 = Offset(host.width / 2 - 32, host.top);
var pLeft3 = Offset(host.width / 2 - 24, host.top - 8);
var pTop = Offset(host.width / 2, host.top - 28);
var pRight1 = Offset(host.width / 2 + 24, host.top - 8);
var pRight2 = Offset(host.width / 2 + 32, host.top);
var pRight3 = Offset(host.width / 2 + 45, host.top);
path
..moveTo(host.left, host.top)
..lineTo(pLeft1.dx, pLeft1.dy)
..quadraticBezierTo(pLeft2.dx, pLeft2.dy, pLeft3.dx, pLeft3.dy)
..quadraticBezierTo(pTop.dx, pTop.dy, pRight1.dx, pRight1.dy)
..quadraticBezierTo(pRight2.dx, pRight2.dy, pRight3.dx, pRight3.dy)
..lineTo(host.right, host.top)
..lineTo(host.right, host.bottom)
..lineTo(host.left, host.bottom)
..close();
return path;
}
}