【问题标题】:How to prevent rebuild of multiple sliver lists with "infinite" items?如何防止使用“无限”项重建多个条子列表?
【发布时间】:2019-08-20 19:40:57
【问题描述】:

首先想说这与here 正在讨论的内容无关,因为我使用的是 Bloc 模式。

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

我认为也许这可以通过 Keys 解决,但这似乎与此无关?我认为问题在于我正在构建方法中动态重建 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 个项目。当我向下滚动时,会构建更多项目。我停在“INDEX 30”并点击一个项目,这会推动一个新页面。现在的问题:页面加载需要一秒钟。日志显示'INDEX 0' ...'INDEX 30',即所有项目都被重建,导致延迟。我弹出页面,再次重新构建从 0 到 30 的所有项目,导致延迟。

如预期的那样,如果我向下滚动到第二个 SliverList,则整个第一个 SliverList 和第二个 SliverList 的延迟构建项都会在 push/pop 时重建。

预期行为:应该只重建周围的项目。

【问题讨论】:

  • 这并没有错。 “导致延迟”是什么意思?
  • 重建项目时它会挂起一秒钟。就像,它滞后,冻结。
  • 我在您的构建方法中没有看到任何令人惊讶的地方。这可能是您的 BLoC 问题。你介意分享一下吗?
  • 当然!感谢您的时间。 pastebin.com/mKKx8EFd
  • @RémiRousselet 不想打扰你,但你有没有偶然看到这个?

标签: flutter


【解决方案1】:

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

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

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

【讨论】:

    猜你喜欢
    • 2021-01-05
    • 1970-01-01
    • 2014-09-18
    • 1970-01-01
    • 1970-01-01
    • 2011-12-27
    • 1970-01-01
    • 1970-01-01
    • 2021-03-15
    相关资源
    最近更新 更多