如何防止用“无限"重建多个条列表.项目? [英] How to prevent rebuild of multiple sliver lists with "infinite" items?

查看:67
本文介绍了如何防止用“无限"重建多个条列表.项目?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先要说这与正在讨论的内容无关此处当我使用Bloc模式时.

Want to start off by saying this is unrelated to what is being discussed here as I use the Bloc pattern.

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

I have a widget where I create a CustomListView with multiple SliverLists based on the items returned by the StreamBuilder on top of the CustomListView. Each SliverList is infinite in the sense that the childCount is set to null. This is for lazy loading purposes. The problem is that when I push to and pop back from a page, all the items of all the SliverLists are rebuild, which causes a delay, especially when I'm already pretty far down the list.

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

I thought perhaps this might be solvable with Keys, but this seems to be unrelated to that? I think the issue is that I'm rebuilding the list of SliverLists dynamically in the build method (see build() in _ItemsBrowserState). The solution that I can think of is storing these widgets inside the state, but that just seems like I'm treating the symptom rather than the cause? I feel the same way about using AutomaticKeepAliveClientMixin, but feel free to change my mind on this.

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的所有项目,从而导致延迟.

Behaviour: I open the page and see the first list. In the logs I see 'INDEX 0', 'INDEX 1', .... 'INDEX 8' (see build() in ItemList), because Flutter lazily builds only the first 9 items. As I scroll down more items are build. I stop at 'INDEX 30' and tap on a item, which pushes a new page. Now the problem: The page loading takes a sec. The logs show 'INDEX 0' ... 'INDEX 30', i.e. all the items are rebuild, causing a delay. I pop the page, and again all items from 0 to 30 are rebuild, causing a delay.

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

As expected, If I scroll down to the second SliverList, the entirety of the first SliverList and the lazily build items of the second SliverList are all rebuild on push/pop.

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

Expected behavior: Only the surrounding items should be rebuild.

推荐答案

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

Ladies and gentleman, we got him:

slivers.add(ItemList(blocs[i], key: UniqueKey()));

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

Replacing the UniqueKey with a ValueKey (or removing it) removed the awful delay!

这篇关于如何防止用“无限"重建多个条列表.项目?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆