前言:
在 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這種方式既實現了頁面狀態的保持,又具有類似惰性求值的功能,對於未使用的頁面狀態不會進行實例化,減小了應用初始化時的開銷。實際開發中看情況選擇實現方式。