开发中遇到一个问题,譬如有如下视图:

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: <Widget>[
          Container(color: Colors.redAccent,),
          Container(color: Colors.blueAccent,),
        ],
      ),
    );
  }
}

PageView相当于Android的ViewPager, 调用PageController.jumpToPage(index)没有问题,如期滑动到指定页面;但是将PageView的children改成如下形式:

        children: <Widget>[
          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是所有界面开发最重要的视图控件之一, 如果要使它的列表项视图常驻这可怎么解。。。

不管原理怎样,现在需要的效果是使视图常驻,立马找到了KeepAliveAutomaticKeepAlive控件,然而让人费解的是这两个控件必须声明祖先节点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<StatefulWidget> createState() => _AliveState();
}

class _AliveState extends State<AliveKeeper>
    with AutomaticKeepAliveClientMixin<AliveKeeper> {

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);

    return widget.child;
  }
}

对应改成:

        children: <Widget>[
          AliveKeeper(child: Builder(builder: (ctx) {
            print('jumptTo111');
            return Container(color: Colors.redAccent,);
          },),),
          Container(color: Colors.blueAccent,),
        ],

builder方法果然不再被调用,而这样一个小巧而实用的控件正是我们需要的~

这种做法肯定不能适用所有情况,所以一定要分清哪种情况下需要让视图对象常驻。另外一个让人忧心的是flutter开发过程中必须对控件非常熟悉,否则稍微不注意用错了控件就会导致视图重建,很多时候定位都不好定位!


林鹿
21 声望5 粉丝