基本上每个app都含有一个底部导航栏,Flutter也提供了相关控件,本博主要从两个方面讲解底部导航栏BottomNavigationBar
首先看看BottomNavigationBar的使用方法,先上代码
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Title',
theme: ThemeData(
primaryColor: Color(0xFF2B9242),
),
home: MainPage()
);
}
}
class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _selectedTab = 0;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: _buildContent(),
bottomNavigationBar:
_BottomTabs(selectedTabIndex: _selectedTab, onTap: _tabSelected),
);
}
_buildContent() {
return Text('this is content');
}
_tabSelected(int tabIndex) {
setState(() {
// update content
});
}
}
class _BottomTabs extends StatelessWidget {
_BottomTabs({@required this.selectedTabIndex, @required this.onTap});
final int selectedTabIndex;
final ValueChanged<int> onTap;
@override
Widget build(BuildContext context) {
// TODO: implement build
return BottomNavigationBar(
onTap: this.onTap,
type: BottomNavigationBarType.fixed,
fixedColor: Colors.green.shade800,
currentIndex: selectedTabIndex,
iconSize: 28,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
title: Text("首页"),
icon: ImageIcon(AssetImage("image/image1.png")),
),
BottomNavigationBarItem(
title: Text("采购/销售"),
icon: ImageIcon(AssetImage("image/image2.png"))),
BottomNavigationBarItem(
title: Text("出入库"),
icon: ImageIcon(AssetImage("image/image3.png")),
),
BottomNavigationBarItem(
title: Text("基础资料"),
icon: ImageIcon(AssetImage("image/image4.png")),
),
BottomNavigationBarItem(
title: Text("待办"),
icon: ImageIcon(AssetImage("image/image5.png"))),
]);
}
}
简单起见, 暂时还没处理根据tab切换内容, BottomNavigationBar的构建有一个type,我们一般使用fixed类型,使用fixed类型时,fixedColor就表示选中时的文字和图片颜色,切换时如果没设置activeIcon,会将图标使用fixedColor进行tint处理,选中时的tab文字会变大,这个我也没找到保持一样大的方法,还有一点值得注意的地方就是icon一般需要使用Icon或ImageIcon,否则会使BottomNavigationBar高度不正常,下面我们来处理页面切换,我们先用TabbarView来显示内容部分,替换_buildContent方法的内容
_buildContent() {
return TabBarView(
controller: _tabController,
physics: const NeverScrollableScrollPhysics(),
children: <Widget>[
HomePage(),
Center(
child: Text(
"Order",
style: TextStyle(
fontSize: 15,
),
),
),
Center(
child: Text("Warehouse"),
),
Center(
child: Text("Base Infra"),
),
Center(
child: Text("Pending"),
),
],
);
}
TabBarView构建需要用到TabController, 通过设置TabController的index来切换对应的页面
子控件相应的BottomNavigationBar切换时的页面, 先定义TabController
class _MainPageState extends State<MainPage>
with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 5, vsync: this);
}
@override
void dispose() {
super.dispose();
_tabController.dispose();
}
// ignore othe non related code
}
下面我们来实现BottomNavigationBar切换时的回调方法
_tabSelected(int tabIndex) {
setState(() {
_tabController.index = tabIndex;
_selectedTab = tabIndex;
});
}
到此我们就实现了BottomNavigationBar页面的切换功能, 大家可能注意到我构建的第一个页面用的是HomePage控件,我这里还没定义,先可以使用Text代替,下面为了显示BottomNavigationBar + TabbarView页面状态重置的现象,我们来构建一个StatefullWidget的控件HomePage
class HomePage extends StatefulWidget {
HomePage();
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Center(
child: FlatButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => Scaffold(
appBar: AppBar(elevation: 0,),
body: Center(
child: Text("Back"),
),
)));
},
child: Text("Home")),
);
}
@override
void dispose() {
super.dispose();
}
}
内容很简单,我们在dispose方法加一个断点,会发现每次切换到其他页时都会调用到,说明状态重置了,如果我们显示静态页面倒也没我们问题,但涉及到网络请求数据时会发现都会重复请求一遍,那么问题来了,我们该怎么解决呢? 最简单的就是页面的State类 mixin AutomaticKeepAliveClientMixin
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Center(
child: FlatButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => Scaffold(
appBar: AppBar(elevation: 0,),
body: Center(
child: Text("Back"),
),
)));
},
child: Text("Home")),
);
}
@override
void dispose() {
super.dispose();
}
}
再次运行切换到其他页面,dispose就不会调用到了,说明没有重置State,mixin AutomaticKeepAliveClientMixin 主要注意两点
第二种解决方法就是内容页不用TabbarView管理,改用IndexedStack,我们重新实现_buildContent()方法
_buildContent() {
return IndexedStack(
index: _selectedTab,
children: <Widget>[
HomePage(),
Center(
child: Text(
"Order",
style: TextStyle(
fontSize: 15,
),
),
),
Center(
child: Text("Warehouse"),
),
Center(
child: Text("Base Infra"),
),
Center(
child: Text("Pending"),
),
],
);
}
IndexedStack控件显示索引为index的child Widget,其他的不显示, Stack控件通过层叠的形式显示所有子控件,学过android的可能已经发现它和FrameLayout布局类似,其实我们也可以通过
Offstage + Stack实现类似的效果,具体怎么用大家自己试试,BottomNavigationBar就讲到这里,如果理解不对的地方欢迎大家指正,如需转载请注明出处