前言
最近在用flutter寫一個小項目,在寫主頁面(底部導航欄+子頁面)時遇到的一個問題:當點擊底部item切換到另一頁面, 再返回此頁面時會重走它的initState方法(我們一般在initState中發起網絡請求,或者初始化的操作),導致不必要的開銷
根據Tab動態加載頁面
我們先定義兩個頁面PageA和PageB
class PageA extends StatefulWidget {
_PageAState createState() => _PageAState();
}
class _PageAState extends State<PageA> {
@override
void initState() {
super.initState();
print("pageA init state");
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.orangeAccent,
child: Center(
child: Text("page A"),
),
);
}
}
class PageB extends StatefulWidget {
_PageBState createState() => _PageBState();
}
class _PageBState extends State<PageB> {
@override
void initState() {
super.initState();
print("pageB init state");
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.blueAccent,
child: Center(
child: Text("page B"),
),
);
}
}
定義Tab主頁面
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _tabIndex = 0;
List<Widget> _tabWidget = [
PageA(),
PageB()
];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'tab demo',
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: _createBottomItems(),
currentIndex: this._tabIndex,
onTap: (index) {
setState(() {
this._tabIndex = index;
});
},
),
body: this._tabWidget.elementAt(this._tabIndex),
),
);
}
}
// 創建底部導航item
List<BottomNavigationBarItem> _createBottomItems() {
return [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("首頁")
),
BottomNavigationBarItem(
icon: Icon(Icons.insert_emoticon),
title: Text("我的")
)
];
運行后發現,每次切換tab都會調用initState,這顯然不符合我們的正常的需求,有下面兩種解決方式
IndexedStack
IndexedStack可以控制子元素的顯示和隱藏,並且會緩存所有的元素,不會每次都重新創建子元素
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'tab demo',
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: _createBottomItems(),
currentIndex: this._tabIndex,
onTap: (index) {
setState(() {
this._tabIndex = index;
});
},
),
body: IndexedStack(
children: this._tabWidget,
index: this._tabIndex,
)
),
);
}
運行后發現還是有個問題,IndexedStack在初始化的時候會初始化所有的子元素,pageA和pageB的initState會同時調用,這明顯還是不符合我們的需求
正確來說應該是切換到具體頁面的時候才進行初始化,而不是一開始就加載所有的頁面的數據,避免資源浪費
PageView + AutomaticKeepAliveClientMixin
使用PageView支持多個view切換,並且不會一次加載完所有的頁面
PageController _pageController;
@override
void initState() {
super.initState();
this._pageController =PageController(initialPage: this._tabIndex, keepPage: true);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'tab demo',
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: _createBottomItems(),
currentIndex: this._tabIndex,
onTap: (index) {
setState(() {
this._tabIndex = index;
_pageController.jumpToPage(index);
});
},
),
body: PageView(
children: this._tabWidget,
controller: _pageController,
),
),
);
}
}
使用PageView可以正常切換,但是每次切換Tab的時候還是會重復調用initState,我們還需要在子頁面實現AutomaticKeepAliveClientMixin
class _PageAState extends State<PageA> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
...
}
class _PageBState extends State<PageB> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
...
}
實現了AutomaticKeepAliveClientMixin就不會每次切換Tab都調用initState了,這也是google推薦的方式
最后發現PageView可以左右滑動切換,這個可以通過設置physics為NeverScrollableScrollPhysics()來禁止滑動
PageView(
children: this._tabWidget,
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
)
參考來源:https://blog.bombox.org/2019-02-23/flutter-tab-keep-alive/#more