iOS开发Flutter探索-State状态保存(10)
前言
有时候我们不希望某个页面每次打开时都重新加载,比如就我们之前的Tabbar结构的页面,每当我们在切换Tab的时候都会执行void initState()
,这就意味着页面每次都会重新渲染,之所以这样就是因为我们的State
状态没有保存,如下图所示:
[没有状态保存效果图]
保存State状态
给当前State
类添加一个扩展(这里就用扩展这个词吧,其实类似于iOS下的Category
),一个系统的扩展类AutomaticKeepAliveClientMixin
,并重写wantKeepAlive
方法,让一个普通的State
类,具有保存状态的能力。
在Dart语法中通过使用with
关键字来添加扩展:
// 通过with 添加扩展
class _PendCourseState extends State<PendCourse> with AutomaticKeepAliveClientMixin<PendCourse>{
List<ChatUser> _datas = [];
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true; //返回true,具备保存能力
@override
void initState() {
// TODO: implement initState
super.initState();
print('待上课程:initState() 执行了');
}
}
bool get wantKeepAlive => true;
之后,当前State
就具备保存能力了,也就意味着重复切换Tab后,void initState()
就不会重复执行了(由原来的viewWillAppear()
变成了viewDidLoad()
)。
问题
按照上面方式修改后,发现切换Tab后void initState()
依然重复执行了,这是为什么吶?这里我们看下我们之前root_page.dart
里面是如何配置我们的tabbar结构的:
class _RootPageState extends State<RootPage> {
int _currentIndex = 0;
// ViewControllers
List <Widget> _viewControllers = [
PendCourse(), //待上课程
TakenCourse(), //已上课程
ExamPage(), //水平测试
PersonalPage(), //我
];
// tabBarItems
List <BottomNavigationBarItem> _tabBarItems = [
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
title: Text('待上课程')
),
BottomNavigationBarItem(
icon: Icon(Icons.history),
title: Text('已上课程')
),
BottomNavigationBarItem(
icon: Icon(Icons.perm_camera_mic),
title: Text('水平测试')
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text('我')
),
];
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
bottomNavigationBar: BottomNavigationBar(
onTap: (int index){
_currentIndex = index;
setState(() {});
},
selectedFontSize: 12.0,
type:BottomNavigationBarType.fixed,
fixedColor: Colors.green,
currentIndex: _currentIndex,
items: _tabBarItems,
),
body: _viewControllers[_currentIndex],
),
);
}
}
这里我们是通过一个_viewControllers
的List,把4个子页面放在了里面,全局有一个_currentIndex
,当onTap
回调后后,更新_currentIndex
的值,执行setState ()
后,body
对应的widget
页面发生改变。而问题也就出在这里,当body
部分发生改变时,根据Flutter的底层渲染逻辑,这里会移除掉之前的Widget
,并重新创建新的Widget
,我们之前在_viewControllers
放的子页面,并不像iOS下是一个实例对象,存在就直接拿来使用。在Flutter 中 setState ()
后界面会被重新绘制,而body
部分只知道我要渲染一个什么样的widget
,而该类型的widget
每次都是会重新创建,这也就意味着我们在Tab切换时,每次都是重新创建,所以每次都执行了initState()
。
显然我们现在的方式是不合理的,那在Flutter中如何管理这样的子页面,而避免重复渲染呐?
这就要用到一个新的部件了:PageView()
,内部的2个关键属性:
-
controller
:这里需要一个PageController
类型,它主要是用来管理子页面的,比如页面的切换; -
children
:<Widget>[]
,这里就是我们的子页面的集合,将之前的定义的_viewControllers
直接交给它就好了;
final PageController _controller = PageController(
initialPage: 0, //默认显示第0个页面
);
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
bottomNavigationBar: BottomNavigationBar(
onTap: (int index){
_currentIndex = index;
setState(() {});
_controller.jumpToPage(index);
},
selectedFontSize: 12.0,
type:BottomNavigationBarType.fixed,
fixedColor: Colors.green,
currentIndex: _currentIndex,
items: _tabBarItems,
),
body: PageView(
controller: _controller,
children: _viewControllers,
),
),
);
}
子页面切换通过_controller.jumpToPage(index);
来实现。
这样子页面也就不会重新创建渲染了,我们的状态保存也就能正常实现了。
- 小细节:使用
PageView()
后,你会发现我们的子页面可以通过左右滑动来切换,这也是PageView()
的一个默认方式,当我们不想要这个效果的时候,可以通过PageView()
内的physics
属性修改它的默认选项。
PageView(
physics: NeverScrollableScrollPhysics(),
//AlwaysScrollableScrollPhysics(), 默认方式
//NeverScrollableScrollPhysics(),
controller: _controller,
children: _viewControllers,
),
总结
学习是一个循序渐进的过程,我们总是在踩坑中不断的前行,把坑填平了也就意味着我们在这个新的东西面前立了足,就可能进行更多为什么的探索了。