由于flutter热重载的特性,widget改变或者消失就会直接销毁,下次直接创建新的。这样的话,tabBar管理的几个根页面也会出现这样的问题,在底部切换tab时,显示的页面会重新创建,重新init,每次切换都需要重新创建,重新渲染页面,重新请求数据,加载数据。
对于正常的业务逻辑和用户体验来说肯定是不能接受的。
我们需要保持根页面的数据和widget树,该怎么处理呢?
方案一、使用 IndexedStack
IndexedStack继承自Stack,它的作用是显示第index个child,其它child在页面上是不可见的,但所有child的状态都被保持,除非应用杀死。所以这个Widget可以实现我们的需求
代码处理:将tabBar页面的body用IndexedStack包裹即可
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('demo'),
),
bottomNavigationBar: BottomNavigationBar(
items: items,
currentIndex: currentIndex,
onTap: onTap),
// body: bodyList[currentIndex]
body: IndexedStack(
index: currentIndex,
children: bodyList,
));
}
方案二 使用Offstage 结合Stack控件
基本原理和使用IndexedStack一样,都是控制控件是否显示
代码处理:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('demo'),
),
bottomNavigationBar: BottomNavigationBar(
items: items, currentIndex: currentIndex, onTap: onTap),
// body: bodyList[currentIndex],
body: Stack(
children: [
Offstage(
offstage: currentIndex != 0,
child: bodyList[0],
),
Offstage(
offstage: currentIndex != 1,
child: bodyList[1],
),
Offstage(
offstage: currentIndex != 2,
child: bodyList[2],
),
],
));
}
}
方案三:使用AutomaticKeepAliveClientMixin
这里操作相对来说比较复杂,需要在两个地方处理
1、tabbar页面
第一步:创建PageController 并制定初始选中页面
第二步:body使用PageView,并制定controller
第三步:点击时,使用_controller.jumpToPage(index)
代码操作:
class _WeChartTabbarState extends State<WeChartTabbar> {
List <Widget> _pages = [];
List<BottomNavigationBarItem> _items = [];
int _currentIndex = 0; // 当前索引
//第一步:创建PageController 并制定初始选中页面
PageController _controller = PageController(initialPage: 0); // 页面控制器
@override
void initState() {
super.initState();
initPages();
}
@override
Widget build(BuildContext context) {
return Scaffold(
//第二步:body使用PageView,并制定controller
body: PageView(
physics: NeverScrollableScrollPhysics(),
controller: _controller,
children: _pages,
),
bottomNavigationBar:BottomNavigationBar(
selectedFontSize: MyDimens.size_little,
unselectedFontSize: MyDimens.size_little,
selectedLabelStyle: TextStyle(color: Colors.green),
fixedColor: MyColor.primary_pressed,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
onTap: (index) {
//第三步:点击时,使用_controller.jumpToPage(index)
_controller.jumpToPage(index);
setState(() {
_currentIndex = index;
});
},
items: _items,
)
);
}
initPages(){
_pages = [ChatPage(), FriendsPage(),FindPage(),MyPage()];
_items = [
_bottomItem("images/tabbar_chat.png", "images/tabbar_chat_hl.png", "聊天", 0),
_bottomItem("images/tabbar_friends.png", "images/tabbar_friends_hl.png", "好友", 1),
_bottomItem("images/tabbar_discover.png", "images/tabbar_discover_hl.png", "发现", 2),
_bottomItem("images/tabbar_mine.png", "images/tabbar_mine_hl.png", "我的", 3),
];
}
// 底部导航item
BottomNavigationBarItem _bottomItem(String icon, String activeIcon, String label, int index) {
return BottomNavigationBarItem(
icon: Image.asset(icon, width: 24,height: 24,),
activeIcon: Image.asset(activeIcon, width: 24,height: 24,),
label: label,
);
}
}
1、需要保持状态的页面
第一步:状态类继承 AutomaticKeepAliveClientMixin
第二步:重写方法 wantKeepAlive => true
第三步:build方法中,写父类build方法: super.build(context);
代码操作:
class ChatPage extends StatefulWidget {
const ChatPage({ Key? key }) : super(key: key);
@override
_ChatPageState createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage>
with AutomaticKeepAliveClientMixin{
//第一步:继承AutomaticKeepAliveClientMixin(上面代码)
//第二步:重写方法 wantKeepAlive => true
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
//第三步:build方法中,写父类build方法: super.build(context);
super.build(context);
return Scaffold(
...
)
}
}
三种方法,推荐使用第三种
前两种方法会加载第一个页面时把所有页面都创建并加载出来,会造成性能浪费这显然是不和常理的。而且对于一些数据经常刷新的页面,提前加载数据没啥用处,而且用户体验会不好。
而第三种,是页面需要显示时再创建页面加载页面,更符合逻辑一点