'ExpansionTile lagging when expanding with a lot of children

Hello !

Everything is in the title.

I have an expansion tile to show members of a group. When lots of people are in a group (here 47), the expansion is not smooth at all and it simply blinks and opens.

Is there a way to make the animation smoother when dealing with a lot of children ?

Thanks in advance !



Solution 1:[1]

I realized the reason is that when you expand one panel setState is triggered for all children. Here's my solution. I made my own expansion panel so that when one setstate is triggered it doesn't affect the whole thing. It works smoother for me. Be careful where and how you call setstate. The Column widget can become a template and used in a ListView.

class TestPage extends StatefulWidget {
  const TestPage({Key? key}) : super(key: key);

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

class _TestPageState extends State<TestPage> {

  bool isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ListView(
          children: [
            Column(
              children: [
                ListTile(
                  onTap: () {
                    setState(() => isExpanded = !isExpanded);
                  },
                  title: Text('Title widget'),
                ),
                AnimatedSize(
                  duration: Duration(milliseconds: 250),
                  curve: Curves.fastOutSlowIn,
                  child: AnimatedCrossFade(
                    firstChild: Container(height: 0.0),
                    secondChild: Column(
                      children: [
                        Text('widget 1'),
                        Text('widget 2'),
                        Text('widget 3'),
                        Text('widget 4'),
                        Text('widget 5'),
                      ],
                    ),
                    firstCurve:
                        const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
                    secondCurve:
                        const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
                    sizeCurve: Curves.fastOutSlowIn,
                    crossFadeState: isExpanded
                        ? CrossFadeState.showSecond
                        : CrossFadeState.showFirst,
                    duration: Duration(milliseconds: 250),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Solution 2:[2]

Just built up on @BananaMaster's answer, the little tweak adds an arrow to the tile and some shadows to make it all look more like the original ExpansionPanel.

class TestPage extends StatefulWidget {
  const TestPage({Key? key}) : super(key: key);

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

class _TestPageState extends State<TestPage> {

  bool isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ListView(
          children: [
            Column(
              children: [
                ListTile(
                  onTap: () {
                    setState(() => isExpanded = !isExpanded);
                  },
                  leading: const Text(
                    'Title goes here',
                    style: TextStyle(fontSize: 17),
                  ),
                  title: Align(
                    child: isExpanded == true
                        ? const Icon(Icons.arrow_drop_up)
                        : const Icon(Icons.arrow_drop_down),
                    alignment: Alignment.centerRight,
                  ),
                  margin: const EdgeInsets.all(5),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: const BorderRadius.only(
                        topLeft: Radius.circular(5),
                        topRight: Radius.circular(5),
                        bottomLeft: Radius.circular(5),
                        bottomRight: Radius.circular(5)),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.grey.withOpacity(0.5),
                        spreadRadius: 1,
                        blurRadius: 1,
                        offset:
                            const Offset(0, 1),
                      ),
                    ],
                  ),
                ),
                AnimatedSize(
                  duration: Duration(milliseconds: 250),
                  curve: Curves.fastOutSlowIn,
                  child: AnimatedCrossFade(
                    firstChild: Container(height: 0.0),
                    secondChild: Column(
                      children: [
                        Text('widget 1'),
                        Text('widget 2'),
                        Text('widget 3'),
                        Text('widget 4'),
                        Text('widget 5'),
                      ],
                    ),
                    firstCurve:
                        const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
                    secondCurve:
                        const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
                    sizeCurve: Curves.fastOutSlowIn,
                    crossFadeState: isExpanded
                        ? CrossFadeState.showSecond
                        : CrossFadeState.showFirst,
                    duration: Duration(milliseconds: 250),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 TheCoder