Flutter: How to scroll an ExpansionTile when it is expanded?

Flutter: How to scroll an ExpansionTile when it is expanded?

Hi, in this article, I’ll focus on ExpansionTile and how you can use it in your app or web app. If you are not familiar with ExpansionTile, you can check my previous article on it: Flutter: Expansion/Collapse view

Let’s begin!

So, I need a way to scroll my ExpansionTile up automatically so that the user doesn’t have to do it manually to see its content.

Currently, I have to manually scroll up:

For the above example, I have used ListView(without any constraints) that will contain the ExpansionTiles.

ListView(
  shrinkWrap: true,
  primary: false,
  children: [
    listItem(title: "See more", icon: Icons.dashboard_rounded),
    listItem(title: "Help & Support", icon: Icons.help_rounded),
    listItem(title: "Setting & Privacy", icon: Icons.settings),
    ListTile(
      leading: Icon(
        Icons.open_in_new_rounded,
        size: 40,
      ),
      title: Text(
        "Share",
        style: TextStyle(fontSize: 17),
      ),
    )
  ],
)
Widget listItem({int index, String title, IconData icon}) {
  return Material(
    color: Colors.transparent,
    child: Theme(
      data: ThemeData(accentColor: Colors.black),
      child: ExpansionTile(
        leading: Icon(
          icon,
          size: 40,
        ),
        title: Text(
          title,
          style: TextStyle(fontSize: 17),
        ),
        children: <Widget>[for (int i = 0; i < 5; i++) cardWidget()],
      ),
    ),
  );
}
Widget cardWidget() {
  return Padding(
    padding: const EdgeInsets.only(top: 5.0, bottom: 8),
    child: Container(
      width: MediaQuery.of(context).size.width * 0.91,
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 15),
      decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(8),
          boxShadow: [
            BoxShadow(
                offset: Offset(1, 3), color: Colors.grey[300], blurRadius: 5),
            BoxShadow(
                offset: Offset(-1, -3),
                color: Colors.grey[300],
                blurRadius: 5)
          ]),
      child: Row(
        children: [
          Icon(
            Icons.image_rounded,
            size: 22,
          ),
          SizedBox(
            width: 10,
          ),
          Text(
            "Title of Card",
            style: TextStyle(fontSize: 15),
          )
        ],
      ),
    ),
  );
}

To automate the process of the scroll in ExpansionTile, I need to know:

1. How much I need to scroll after expansion?

In this particular case, I do not know how many pixels the ExpansionTile height will take or what will be its context after expansion. So, we can use a GlobalKey and then check its current context at runtime to make it visible later.

I’ll use the key property to get the current context of the ExpansionTile.

//..
final GlobalKey expansionTileKey = GlobalKey();
//..

And the onExpansionChanged property to check the expansion status.

//..
  key: expansionTileKey,
  onExpansionChanged: (value) {
    if (value) { // Checking expansion status

    }
  },
//..

2. How to scroll after getting the context?

I will use Scrollable widget to make the current context visible after expansion. As the name suggests, it is a widget that scrolls. Scrollable class A widget that scrolls. Scrollable implements the interaction model for a scrollable widget, including gesture…api.flutter.dev

I created a private function _scrollToSelectedContent which will receive the key and scroll the widget with a duration:

void _scrollToSelectedContent({GlobalKey expansionTileKey}) {
    final keyContext = expansionTileKey.currentContext;
    if (keyContext != null) {
      Future.delayed(Duration(milliseconds: 200)).then((value) {
        Scrollable.ensureVisible(keyContext,
            duration: Duration(milliseconds: 200));
      });
    }
  }

Here, I am checking whether the current context is null. If it isn’t, I delayed the Scrollable widget. The ExpansionTile’s expansion animation takes 200ms to show the content, as shown in its definition: const Duration _kExpand = Duration(milliseconds: 200); therefore it is a good idea to wait for expansion animation to finish before starting scrolling so you can get the updated context.

You can also add the curve and alignment to the way of the scroll.

Now I will call this function within the onExpansionChanged property and pass the key:

//..
  key: expansionTileKey,
  onExpansionChanged: (value) {
     if (value) {
       _scrollToSelectedContent(expansionTileKey: expansionTileKey);
     }
  },
//..
Widget listItem({int index, String title, IconData icon}) {
    final GlobalKey expansionTileKey = GlobalKey();
    return Material(
      color: Colors.transparent,
      child: Theme(
        data: ThemeData(accentColor: Colors.black),
        child: ExpansionTile(
          key: expansionTileKey,
          onExpansionChanged: (value) {
            if (value) {
              _scrollToSelectedContent(expansionTileKey: expansionTileKey);
            }
          },
          leading: Icon(
            icon,
            size: 40,
          ),
          title: Text(
            title,
            style: TextStyle(fontSize: 17),
          ),
          children: <Widget>[for (int i = 0; i < 5; i++) cardWidget()],
        ),
      ),
    );
  }

And I am done! After this, my ExpansionTile will auto-scroll to display its content:

Thank you for reading, if you enjoyed the article make sure to give a clap (👏)! You can connect with me on Twitter, LinkedIn and find some of my work on GitHub and Codepen. And for more such articles you can support me by buying me a coffee

Reference: How to scroll an ExpansionTile / Listview when a tile is expanded?