如何防止带有“无限”项的多个条子列表的重建?

时间:2019-03-29 19:51:14

标签: flutter

首先要说的是,这与我在使用Bloc模式时正在讨论的here无关。

我有一个小部件,可在其中基于StreamBuilder在CustomListView顶部返回的项目创建具有多个SliverList的CustomListView。在将childCount设置为null的意义上,每个SliverList是无限的。这是为了延迟加载的目的。问题是,当我推入页面并从页面弹出时,所有SliverList的所有项目都将重建,这会导致延迟,尤其是当我已经很远的时候。

我认为这也许可以通过Keys解决,但这似乎与此无关吗?我认为问题在于我正在build方法中动态重建SliverLists列表(请参见_ItemsBrowserState中的build())。我能想到的解决方案是将这些小部件存储在状态内,但这似乎只是在处理症状而不是原因?我对使用AutomaticKeepAliveClientMixin的感觉相同,但是随时可以改变主意。

class ItemsBrowser extends StatefulWidget {
  final RepositoryBloc repoBloc;

  ItemsBrowser({Key key, @required this.repoBloc}) : super(key: key);

  @override
  _ItemsBrowserState createState() => _ItemsBrowserState();
}

class _ItemsBrowserState extends State<ItemsBrowser> {
  ScrollController _scrollController;
  ItemBrowsersBloc bloc;
  List<ItemBrowserBloc> blocs = [];
  int atBloc = 0;

  bool _batchLoadListener(ScrollNotification scrollNotification) {
    if (!(scrollNotification is ScrollUpdateNotification)) return false;
    if (_scrollController.position.extentAfter > 500) return false;
    if (atBloc == blocs.length) return false;
    if (blocs[atBloc].isLoading.value) return false;
    if (blocs[atBloc].wasLastPage) atBloc++;
    if (atBloc < blocs.length) blocs[atBloc].loadNextBatch();
    return false;
  }

  @override
  void initState() {
    super.initState();
    bloc = ItemBrowsersBloc(widget.repoBloc);
    bloc.collections.listen((collections) {
      if (_scrollController.hasClients) _scrollController.jumpTo(0.0);
      _disposeItemBlocs();
      atBloc = 0;
      blocs = [];
      for (var i = 0; i < collections.length; i++) {
        var itemBloc = ItemBrowserBloc(collections[i], initLoad: i == 0);
        blocs.add(itemBloc);
      }
    });
    _scrollController = ScrollController();
  }

  void _disposeItemBlocs() {
    if (blocs != null) {
      for (var b in blocs) {
        b.dispose();
      }
    }
  }

  @override
  void dispose() {
    super.dispose();
    bloc?.dispose();
    _disposeItemBlocs();
  }

  @override
  Widget build(BuildContext context) {
    print('Building Item Browser');
    return StreamBuilder<List<Collection>>(
        stream: bloc.collections,
        builder: (context, snapshot) {
          if (!snapshot.hasData) return Container();

          List<Widget> slivers = [];
          for (var i = 0; i < snapshot.data.length; i++) {
            slivers.add(ItemList(blocs[i], key: UniqueKey()));
            slivers.add(_buildLoadingWidget(i));
          }

          slivers.add(const SliverToBoxAdapter(
              child: const SizedBox(
            height: 90,
          )));

          return NotificationListener<ScrollNotification>(
            onNotification: _batchLoadListener,
            child: CustomScrollView(
                controller: _scrollController, slivers: slivers),
          );
        });
  }

  Widget _buildLoadingWidget(int index) {
    return StreamBuilder(
      stream: blocs[index].isLoading,
      initialData: true,
      builder: (context, snapshot) {
        return SliverToBoxAdapter(
          child: Container(
            child: snapshot.data && !blocs[index].initLoaded
                ? Text(
                    'Loading more...',
                    style: TextStyle(color: Colors.grey.shade400),
                  )
                : null,
          ),
        );
      },
    );
  }
}

class ItemList extends StatelessWidget {
  final ItemBrowserBloc bloc;

  ItemList(this.bloc, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<bool>(
        stream: bloc.isLoading,
        initialData: true,
        builder: (context, snapshot) {
          var isLoading = snapshot.data;
          var isInitialLoad = isLoading && !bloc.initLoaded;
          return SliverList(
            delegate: SliverChildBuilderDelegate((context, index) {
              // Index:   0       1     2     3
              // Return:  Header  Item  Item  null
              print('INDEX $index');
              if (index == 0) return _buildHeader();
              if (index > bloc.items.value.length) return null;
              // var itemIndex = (index - 1) % bloc.batchSize;
              var itemIndex = index - 1;
              var item = bloc.items.value[itemIndex];
              return InkWell(
                key: ValueKey<String>(item.key),
                child: ItemTile(item),
                onTap: () {
                  Navigator.of(context).push(MaterialPageRoute(
                      builder: (BuildContext context) => ItemPage(item)));
                },
              );
            }, childCount: isInitialLoad ? 0 : null),
          );
        });
  }

  Widget _buildHeader() {
    return Container();
  }
}

行为:我打开页面并看到第一个列表。在日志中,我看到'INDEX 0','INDEX 1',....'INDEX 8'(请参阅ItemList中的build()),因为Flutter懒惰地仅构建了前9个项目。当我向下滚动时,将构建更多项。我在“索引30”处停下来,然后点击一个项目,这会推开一个新页面。现在的问题是:页面加载需要几秒钟。日志显示'INDEX 0'...'INDEX 30',即所有项目都已重建,导致延迟。我弹出页面,再次重建0到30的所有项目,从而导致延迟。

按预期的方式,如果我向下滚动到第二个SliverList,则第一个SliverList的全部和第二个SliverList的延迟构建项都将在push / pop上重建。

预期的行为:仅应重建周围的物品。

1 个答案:

答案 0 :(得分:0)

女士们先生们,我们找到了他:

foreach

用ValueKey替换UniqueKey(或将其删除)消除了可怕的延迟!