TatsuUkraine / flutter_sticky_infinite_list

Multi directional infinite list with Sticky headers for Flutter applications
BSD 2-Clause "Simplified" License
341 stars 31 forks source link

Problem with first item displayed #20

Closed wizzar closed 4 years ago

wizzar commented 4 years ago

Initially, the InfiniteList builder is called with indexes -1, 0 and 1, and the displayed item is the 0, naturally. However, after using setState() inside the InfiniteList items, causing a rebuild, the list item being displayed first is not 0 anymore, it is always the last one, and this gets progressively worse if you traverse the scroll too much before tapping.

To reproduce: click on any date on the first month (should be december at the time of the writing). You will see that the text at the top changes, and the scroll behaves as expected. Now scroll further, to month 2, 3, etc, and click on a day. You will see that the scroll changes it offset weirdly, even thou the date computation is correct.

Upon running the app: image

After clicking on Month 12, day 15: image

After clicking on Month 1, day 8: image

I don't understand why month 2 is being rendered first here, since month 1 is still my first item (index 0 if you will).

This is a bit complex to explain, but this code does exactly that:

class Test extends StatefulWidget {
  @override
  _TestState createState() => _TestState();
}

class _TestState extends State<Test> {
  DateTime date = DateTime.now();
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Column(
          children: <Widget>[
            Container(
              color: Colors.lightBlue,
              child: Text(
                date.toString(),
                style: TextStyle(
                  fontSize: 30,
                ),
              ),
            ),
            Container(
              margin: EdgeInsets.symmetric(vertical: 30),
              height: 300,
              child: InfiniteList(
                scrollDirection: Axis.horizontal,
                direction: InfiniteListDirection.multi,
                builder: (context, index) {
                  var month = DateTime(date.year, date.month + index, 1);
                  var monthNumDays =
                      DateTime(month.year, date.month + index, 0).day;

                  return InfiniteListItem(
                    initialHeaderBuild: true,
                    headerAlignment: HeaderAlignment.centerLeft,
                    headerBuilder: (context) {
                      return Container(
                        color: Colors.white,
                        child: Text(
                          "Month: ${month.month}",
                          style: TextStyle(
                            fontSize: 20,
                          ),
                        ),
                      );
                    },
                    contentBuilder: (context) {
                      return Container(
                        color: RandomColor().randomColor(),
                        child: Padding(
                          padding: const EdgeInsets.only(
                            left: 96, // Necessary to fix header overlay bug.
                          ),
                          child: Row(
                            children: List.generate(monthNumDays, (itemIndex) {
                              return RaisedButton(
                                child: Text("${itemIndex + 1}"),
                                onPressed: () {
                                  setState(() {
                                    date = month.add(Duration(days: itemIndex));
                                  });
                                },
                              );
                            }),
                          ),
                        ),
                      );
                    },
                  );
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}
TatsuUkraine commented 4 years ago

Will take a look at this

Could you explain the issue a little bit more? Not sure that I'm following. Issue is that scroll remains on same position even after you call setState?

wizzar commented 4 years ago

TL;DR; Set a random unique "key" on InfiniteList so that the scroll offset is reset everytime.

Ok, I finally figured it out. The thing here is that the ScrollController saves it's scroll position in the PageStorage of the list it is attached to. So, what is happening, is that the list is rebuilt, but now the new month is at offset 0, but the scroll position is maintained, which throws it off. What must be done here is reset the list scroll offset when it is rebuilt, in my case. The way to do this here, is to set a different key to the list when it is created. This way the widget will not maintain the offset, and everything will work.

This question on SO discusses this well too: https://stackoverflow.com/questions/56364950/listview-doesnt-scroll-to-the-correct-offset-position-after-its-built-again#56365266