ListVIew重新构建后无法滚动到正确的偏移位置

时间:2019-05-29 16:28:51

标签: flutter

我希望列表视图以偏移量开头。我正在尝试通过在ListView中使用以下代码来实现这一点。

  

控制器:ScrollController(initialScrollOffset:30 * ITEM_HEIGHT),

首先在第一次加载时以正确的偏移量加载列表。

通过从父窗口小部件调用set状态再次构建列表时,列表会更新,但是滚动偏移的行为很奇怪。

有两种情况:

  1. 我不滚动列表:此后,如果调用了set状态,则一切正常。列表会更新,并且始终处于正确的偏移量。
  2. 我滚动列表:如果我滚动列表然后重建列表, 滚动偏移量关闭了一些项目。该列表已更新,这很好。

是因为我滚动时会保留最后一个滚动位置,这会抵消我的计算结果吗?我认为这不应该发生,因为它是状态少的小部件。

class DaysManager extends StatelessWidget {
  final int daysBeforeFocusDate = 30;
  final int totalDaysToInit = 61;
  static final double ITEM_HEIGHT = 108.00;

  ScrollController scrollController;

  List<Day> days;

  DaysManager({DateTime focusDate}) {
      final DateTime startDate =
      focusDate.subtract(Duration(days: daysBeforeFocusDate));
      days = List.generate(totalDaysToInit, (int index) {
      return Day(
       date: startDate.add(
        Duration(days: index),
    ),
  );
});

scrollController = ScrollController(initialScrollOffset: 30 *ITEM_HEIGHT);
}

  @override
  Widget build(BuildContext context) {
   return _buildScrollView();
  }

  ListView _buildScrollView() {
    ListView listView = ListView.builder(
    cacheExtent: 0,
    controller: scrollController,
    itemCount: days.length,
    itemBuilder: (BuildContext context, int index) {
      return days[index];
     });

    return listView;
  }
 }

3 个答案:

答案 0 :(得分:2)

我接触了Flutter Slack社区,并得到了可行的答案。全部归功于楼首。这是谈话内容的副本。

ScrollController将其滚动位置保存在其所附的列表的PageStorage记录内……而不是它自己的PageStorage。因此,在重新创建窗口小部件时,由于您没有为列表视图窗口小部件指定新的key,因此它会重用相同的键(为提高性能而进行内部优化的众多优化之一)。您可以通过添加两行来解决此问题:

import 'dart:math';
...
ListView listView = ListView.builder(
  key: ValueKey<int>(Random(DateTime.now().millisecondsSinceEpoch).nextInt(4294967296)),
...

每次重新创建列表时,都需要给列表视图一个新的随机key,以便它不会加载其PageStorage值。

这是示例代码中DaysManager的完整更新代码:

class DaysManager extends StatelessWidget {
  final int daysBeforeFocusDate = 30;
  final int totalDaysToInit = 61;
  static final double ITEM_HEIGHT = 108.00;

  ScrollController scrollController;

  List<Day> days;

  DaysManager({
    DateTime focusDate,
  }) {
    final DateTime startDate = focusDate.subtract(Duration(days: daysBeforeFocusDate));
    days = List.generate(totalDaysToInit, (int index) {
      return Day(
        date: startDate.add(
          Duration(days: index),
        ),
      );
    });

    scrollController = ScrollController(
      initialScrollOffset: 30 *ITEM_HEIGHT,
    );
  }

  @override
  Widget build(BuildContext context) {
    return _buildScrollView();
  }

  ListView _buildScrollView() {
    ListView listView = ListView.builder(
      key: ValueKey<int>(Random(DateTime.now().millisecondsSinceEpoch).nextInt(4294967296)),
      cacheExtent: 0,
      controller: scrollController,
      itemCount: days.length,
      itemBuilder: (BuildContext context, int index) {
        return days[index];
      });

    return listView;
  }
}

答案 1 :(得分:0)

更多代码可能会有所帮助,尽管看起来您是在build方法内部实例化ScrollController。生成方法会不断重建。因此,它将不断使用该initialScrollOffset。尝试在主要build方法之外实例化ScrollController。

修改 添加新代码后,我认为这是您的罪魁祸首:

DaysManager({DateTime focusDate}) {
      final DateTime startDate =
      focusDate.subtract(Duration(days: daysBeforeFocusDate));
      days = List.generate(totalDaysToInit, (int index) {
      return Day(
       date: startDate.add(
        Duration(days: index),
    ),
  );
});

在重建无状态窗口小部件时,似乎好像是在生成列表,这可能有一些奇怪的行为。我认为该类需要放在有状态的小部件中,而应该在initState上生成数据列表,或者可以从诸如继承的小部件之类的其他位置获取该数据,或使用某种架构模式来保存此数据(BLoC或其他任何内容) )。

答案 2 :(得分:0)

真正的问题出在您的Day小部件上,我不知道它的代码,因此我删除了此部分,并使用普通的ListTile进行了工作,并且按预期运行。试试下面的代码(所有内容都与您输入的内容完全相同,我只是删除了Day小部件部分)

class DaysManager extends StatelessWidget {
  final int daysBeforeFocusDate = 30;
  final int totalDaysToInit = 61;
  static final double ITEM_HEIGHT = 108.00;

  ScrollController scrollController;

  DaysManager({DateTime focusDate}) {
    scrollController = ScrollController(initialScrollOffset: 30 * ITEM_HEIGHT);
  }

  @override
  Widget build(BuildContext context) {
    return _buildScrollView();
  }

  ListView _buildScrollView() {
    ListView listView = ListView.builder(
      cacheExtent: 0,
      controller: scrollController,
      itemCount: 1000,
      itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("Item ${index}"),);
      },
    );

    return listView;
  }
}