前言:
在 Flutter 应用中,导航栏切换页面后默认情况下会丢失原页面状态,即每次进入页面时都会重新初始化状态,不仅增加额外开销,而且体验差。
- 使用IndexedStack实现
IndexedStack继承自Stack,它的作用是显示第index个child,其它child在页面上是不可见的,但所有child的状态都被保持,所以这个Widget可以实现我们的需求,我们只需要将现在的body用IndexedStack包裹一层即可
class MainDart extends StatefulWidget {
@override
_MainDartState createState() => _MainDartState();
}
class _MainDartState extends State<MainDart> with TickerProviderStateMixin{
//默认索引
int positionIndex = 0;
//底部导航栏
var mainTitles = ['患者诊疗', '收费','物资', '设置'];
var indexStack;
List<BottomNavigationBarItem> navigationViews;
@override
Widget build(BuildContext context) {
initData();
return Scaffold(
appBar: PreferredSize(
preferredSize:Size.fromHeight(MediaQuery.of(context).size.height * 0.07),
child:SafeArea(
top: true,
child: Offstage(),
),
),
body: indexStack,
bottomNavigationBar: initNavigationBar(),
);
}
@override
void initState() {
super.initState();
navigationViews = <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: const Icon(Icons.home),
title: new Text(mainTitles[0]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.assignment),
title: new Text(mainTitles[1]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.devices_other),
title: new Text(mainTitles[2]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.person),
title: new Text(mainTitles[3]),
backgroundColor: Colors.black,
),
];
}
///初始化数据
void initData() {
indexStack = new IndexedStack(
children: <Widget>[new TreatmentPage(), new ChargePage(),new GoodsPage(), new SetPage()],
index: positionIndex,
);
}
///相当于底部导航栏
BottomNavigationBar initNavigationBar(){
return new BottomNavigationBar(
items: navigationViews.map((BottomNavigationBarItem navigationView) => navigationView).toList(),
currentIndex: positionIndex,
type: BottomNavigationBarType.fixed,
onTap: (index) {
setState(() {
positionIndex = index;
});
},
);
}
}
- 使用Offstage实现
Offstage的作用十分简单,通过一个参数来控制child是否显示,所以我们同样可以组合使用Offstage来实现该需求,其实现原理与IndexedStack类似。
class MainDart extends StatefulWidget {
@override
_MainDartState createState() => _MainDartState();
}
class _MainDartState extends State<MainDart> with TickerProviderStateMixin{
//默认索引
int positionIndex = 0;
//底部导航栏
var mainTitles = ['患者诊疗', '收费','物资', '设置'];
final bodyList = [new TreatmentPage(), new ChargePage(),new GoodsPage(), new SetPage()];
List<BottomNavigationBarItem> navigationViews;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize:Size.fromHeight(MediaQuery.of(context).size.height * 0.07),
child:SafeArea(
top: true,
child: Offstage(),
),
),
body: Stack(
children: [
Offstage(
offstage: positionIndex != 0,
child: bodyList[0],
),
Offstage(
offstage: positionIndex != 1,
child: bodyList[1],
),
Offstage(
offstage: positionIndex != 2,
child: bodyList[2],
),
Offstage(
offstage: positionIndex != 3,
child: bodyList[3],
),
],
),
bottomNavigationBar: initNavigationBar(),
);
}
@override
void initState() {
super.initState();
navigationViews = <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: const Icon(Icons.home),
title: new Text(mainTitles[0]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.assignment),
title: new Text(mainTitles[1]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.devices_other),
title: new Text(mainTitles[2]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.person),
title: new Text(mainTitles[3]),
backgroundColor: Colors.black,
),
];
}
///相当于底部导航栏
BottomNavigationBar initNavigationBar(){
return new BottomNavigationBar(
items: navigationViews.map((BottomNavigationBarItem navigationView) => navigationView).toList(),
currentIndex: positionIndex,
type: BottomNavigationBarType.fixed,
onTap: (index) {
setState(() {
positionIndex = index;
});
},
);
}
}
- 前面在底部导航介绍了使用IndexedStack和Offstage两种方式实现保持页面状态,下面我们使用PageView+AutomaticKeepAliveClientMixin重写之前的底部导航。
class MainDart extends StatefulWidget {
@override
_MainDartState createState() => _MainDartState();
}
class _MainDartState extends State<MainDart> with TickerProviderStateMixin{
//默认索引
int positionIndex = 0;
//底部导航栏
var mainTitles = ['患者诊疗', '收费','物资', '设置'];
final bodyList = [new TreatmentPage(), new ChargePage(),new GoodsPage(), new SetPage()];
List<BottomNavigationBarItem> navigationViews;
final pageController = PageController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize:Size.fromHeight(MediaQuery.of(context).size.height * 0.07),
child:SafeArea(
top: true,
child: Offstage(),
),
),
body: PageView(
controller: pageController,
onPageChanged: onPageChanged,
children: bodyList,
physics: NeverScrollableScrollPhysics(), // 禁止滑动
),
bottomNavigationBar: initNavigationBar(),
);
}
void onPageChanged(int index) {
setState(() {
positionIndex = index;
});
}
@override
void initState() {
super.initState();
navigationViews = <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: const Icon(Icons.home),
title: new Text(mainTitles[0]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.assignment),
title: new Text(mainTitles[1]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.devices_other),
title: new Text(mainTitles[2]),
backgroundColor: Colors.black,
),
new BottomNavigationBarItem(
icon: const Icon(Icons.person),
title: new Text(mainTitles[3]),
backgroundColor: Colors.black,
),
];
}
///相当于底部导航栏
BottomNavigationBar initNavigationBar(){
return new BottomNavigationBar(
items: navigationViews.map((BottomNavigationBarItem navigationView) => navigationView).toList(),
currentIndex: positionIndex,
type: BottomNavigationBarType.fixed,
onTap: (index) {
pageController.jumpToPage(index);
},
);
}
}
然后在bodyList的子页State中继承AutomaticKeepAliveClientMixin并重写wantKeepAlive,以GoodsPage.dart为例:
class GoodsPage extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
GoodsPageState mstate = new GoodsPageState();
return mstate;
}
}
class GoodsPageState extends State<GoodsPage> with AutomaticKeepAliveClientMixin{
@override
Widget build(BuildContext context) {
// TODO: implement build
return Text('goods');
}
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
}
-
使用TabBarView。
PageView和TabBarView的实现原理类似,TabBarView内部也是用的是PageView。 因此两者的使用方式相同。 -
总结:使用IndexedStack和Offstage两种方式实现保持页面状态,但它们的缺点在于第一次加载时便实例化了所有的子页面State。使用TabBarView / PageView+AutomaticKeepAliveClientMixin这种方式既实现了页面状态的保持,又具有类似惰性求值的功能,对于未使用的页面状态不会进行实例化,减小了应用初始化时的开销。实际开发中看情况选择实现方式。