How to prevent Flutter app from scrolling to top after calling setState?
Asked Answered
H

4

14

I have an ExpansionPanelList inside a SingleChildScrollView. Whenever I (un)fold a panel and therefore call setState, the SingleChildScrollView scrolls back to the top. How can I prevent this?

@override
Widget build(BuildContext context) {
  final _scaffoldKey = new GlobalKey<ScaffoldState>();
  return new Scaffold(
    key: _scaffoldKey,
    appBar: new AppBar(
      title: new Text(widget.title),
    ),
    body: new SingleChildScrollView(
      child: new ExpansionPanelList(
        children: <ExpansionPanel>[
          // panels
        ],
        expansionCallback: (int index, bool isExpanded) {
          setState(() {
            // toggle expanded
          });
        },
      ), // ExpansionPanelList
    ), // SingleChildScrollView
  ); // Scaffold
}

This answer suggests using a custom ScrollController with keepScrollOffset set to true, however this is the default value and setting it explicitly to true therefore does not change anything.

Heldentenor answered 1/1, 2019 at 21:39 Comment(0)
H
19

That's because you are using a new Key every time you rebuild the widget (setState).

To fix your issue just move the code below outside the build method

 final _scaffoldKey = new GlobalKey<ScaffoldState>(); 

Like this :

 final _scaffoldKey = new GlobalKey<ScaffoldState>();

    @override
    Widget build(BuildContext context) {
      return new Scaffold(
        key: _scaffoldKey,
        appBar: new AppBar(
          title: new Text(widget.title),
        ),
Hungry answered 1/1, 2019 at 23:10 Comment(1)
Thank you! This also solved another problem that I didn't even realize I had, which is, the ExpansionPanelList wasn't animated at all.Heldentenor
B
0

I was having the same problem, and somehow the answer from @diegoveloper did not do the trick for me.

What i ended up doing was separating the SingleChildScrollView in an independent StatefulWidget: That also fixed the scroll animation.

My code then ended up being something like

Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: MyExpansionList(),
...

class MyExpansionListextends StatefulWidget {

  @override
  _MyExpansionListState createState() => _MyExpansionListState();
}

class _MyExpansionListState extends State<MyExpansionList> {
  @override
  Widget build(BuildContext context) {

    return Scrollbar(
      isAlwaysShown: true,
      showTrackOnHover: true,
      child: SingleChildScrollView(
        child: Column(
          children: [
            ExpansionPanelList(animationDuration: Duration(seconds: 1),

In this way the setState() did update only the ExpansionPanelList/ScrollView and not the whole Scaffold.

I hope this also helps others facing same problem...

Boot answered 21/5, 2021 at 5:30 Comment(0)
K
0

Nothing helped me until I realised that in the build() function I was calling jumpTo() of the controller that was attached to my list. Like this:

@override
Widget build(BuildContext context) {
  if (widget.controller.hasClients) {
    widget.controller.jumpTo(0);
  }
  ...
}

I removed these lines and the problem was gone. Happy coding :)

Keslie answered 17/11, 2022 at 9:7 Comment(0)
K
0

You have to pass a key to the SingleChildScrollView. Otherwise, its state is renewed every setState call.

 final _scrollKey = GlobalKey();

   SingleChildScrollView(
        key: _scrollKey,
        child:
Knight answered 23/1, 2023 at 15:3 Comment(1)
This worked in my case. thank youWye

© 2022 - 2024 — McMap. All rights reserved.