nick.tdr
nick.tdr

Reputation: 4933

ListVIew doesn't scroll to the correct offset position after its built again

I want the listview to start with an offset. Which I am trying to achieve by using below code in the ListView.

controller: ScrollController(initialScrollOffset: 30 * ITEM_HEIGHT),

Initially on the first load the list is loaded with the correct offset.

When the list is built again by calling set state from the parent widget, the list gets updated but the scroll offset behaves weird.

There are two scenarios:

  1. I don't scroll the list: After this if the set state is called everything works fine. List gets updated and is always at the correct offset.
  2. I scroll the list: if I scroll the list and then the list is rebuilt, the scroll offset is off by a few items. The list gets updated which is fine.

Is it because when I scroll it keeps the last scroll position and that offsets my calculation? Which I think should not happen as it is a state less widget.

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;
  }
 }

Upvotes: 1

Views: 3858

Answers (1)

Will Luce
Will Luce

Reputation: 1841

I reached out the the Flutter Slack community and got an answer that works. All credit to Loushou over there. Here's a copy of that conversation.

ScrollController saves it’s scroll position inside the PageStorage record of the list it is attached to… not of it’s own PageStorage. because of this, when the widget is recreated, since you do not specify a new key for the listview widget, it reuses the same key (one of the many optimizations internal to flutter for performance). you can solve this by adding two lines:

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

You need to give the listview a new, random key every time you recreate it, so that it does not load up it’s PageStorage values.

here is the full, updated code for DaysManager from your example code:

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;
  }
}

Upvotes: 8

Related Questions