前言
最近在用flutter写一个小项目,在写主页面(底部导航栏+子页面)时遇到的一个问题:当点击底部item切换到另一页面, 再返回此页面时会重走它的initState方法(我们一般在initState中发起网络请求,或者初始化的操作),导致不必要的开销
根据Tab动态加载页面
我们先定义两个页面PageA
和PageB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 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主页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| 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), ), ); } }
List<BottomNavigationBarItem> _createBottomItems() { return [ BottomNavigationBarItem( icon: Icon(Icons.home), title: Text("首页") ), BottomNavigationBarItem( icon: Icon(Icons.insert_emoticon), title: Text("我的") ) ]; }
|
运行后发现,每次切换tab都会调用initState
,这显然不符合我们的正常的需求,有下面两种解决方式
IndexedStack
IndexedStack
可以控制子元素的显示和隐藏,并且会缓存所有的元素,不会每次都重新创建子元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @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切换,并且不会一次加载完所有的页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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
1 2 3 4 5 6 7 8 9 10 11 12
| 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()
来禁止滑动
1 2 3 4 5
| PageView( children: this._tabWidget, controller: _pageController, physics: NeverScrollableScrollPhysics(), )
|