开发中遇到一个问题,譬如有如下视图:
class _PageState extends State<_HomePage> {
final _controller = PageController(initialPage: 0, keepPage: true);
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _controller,
physics: NeverScrollableScrollPhysics(),
children: [
Container(color: Colors.redAccent,),
Container(color: Colors.blueAccent,),
],
),
);
}
}
PageView
相当于Android的ViewPager
, 调用PageController.jumpToPage(index)
没有问题,如期滑动到指定页面;但是将PageView的children改成如下形式:
children: [
Builder(builder: (ctx) {
print('jumptTo111');
return Container(color: Colors.redAccent,);
},),
Container(color: Colors.blueAccent,),
],
发现每次滑动到第一个页面都会打印jumptTo111
, 这里存在一个问题: Builder的builder方法是在build方法中调用的,意味着Widget.build(BuildContext)
被调用了,因而Container(color: Colors.redAccent,)
重建了(注意Builder
对象并没有重建)。把Builder
换成别的控件也一样,关键是都会走这个控件对象的build方法。
这并不是我们期望的!PageView
一般都是比较大的根节点页面的容器,如果每次滑动都要重建页面那必然引起性能问题!像这种情况我们必然是不希望重建页面的。这真是有点坑,因为明明已经给PageController
设置了keepPage: true
属性,竟然不好使?!不知道 这算不是算是控件的bug,反正在flutter的v1.8.0上总是重建的。后来才搞明白keepPage
这个属性表示的意思是视图被重建后记住滑动到的是第几页,和我们的意图一点关系没有。。。
后来发现ListView也有类似的问题:它接收一个IndexedWidgetBuilder itemBuilder
参数来创建列表项的视图。如果有1-100的列表项,从1滑到100没问题,再从100滑到1还是重建了列表项视图。某些情况下创建好的视图对象只要数据没有变更, 我们当然是不希望再去创建视图,ListView是所有界面开发最重要的视图控件之一, 如果要使它的列表项视图常驻这可怎么解。。。
不管原理怎样,现在需要的效果是使视图常驻,立马找到了KeepAlive
和AutomaticKeepAlive
控件,然而让人费解的是这两个控件必须声明祖先节点SliverWithKeepAliveWidget
,否则就崩溃。好在找到了这篇文章,核心意思是必须通过StatefulWidget的状态对象混入AutomaticKeepAliveClientMixin
这样能够达到视图常驻的目的,形如
class _PageState extends State<_HomePage> with AutomaticKeepAliveClientMixin<_HomePage> {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(...
);
}
}
但我们平常开发肯定不希望再耦合这坨逻辑 所以最好封装成能直接拿来用的控件,因为KeepAlive已经被用了就起名AliveKeeper
吧, 于是有
import 'package:flutter/widgets.dart';
class AliveKeeper extends StatefulWidget {
final Widget child;
const AliveKeeper({Key key, @required this.child}) : super(key: key);
@override
State createState() => _AliveState();
}
class _AliveState extends State
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}
}
对应改成:
children: [
AliveKeeper(child: Builder(builder: (ctx) {
print('jumptTo111');
return Container(color: Colors.redAccent,);
},),),
Container(color: Colors.blueAccent,),
],
builder方法果然不再被调用,而这样一个小巧而实用的控件正是我们需要的~
这种做法肯定不能适用所有情况,所以一定要分清哪种情况下需要让视图对象常驻。另外一个让人忧心的是flutter开发过程中必须对控件非常熟悉,否则稍微不注意用错了控件就会导致视图重建,很多时候定位都不好定位!