Back to Posts

Flutter ExpansionTile: Creating Expandable Lists

14 min read

ExpansionTile is a powerful widget in Flutter that allows you to create collapsible sections in your app. It's perfect for creating FAQs, settings menus, or any hierarchical data display. In this guide, we'll explore how to implement and customize ExpansionTile.

Basic Implementation

Here's a simple ExpansionTile implementation:

ExpansionTile(
  title: Text('Click to Expand'),
  children: [
    ListTile(
      title: Text('Item 1'),
    ),
    ListTile(
      title: Text('Item 2'),
    ),
    ListTile(
      title: Text('Item 3'),
    ),
  ],
)

Customizing ExpansionTile

1. Styling and Decoration

ExpansionTile(
  title: Text(
    'Custom Styled ExpansionTile',
    style: TextStyle(
      fontSize: 18.0,
      fontWeight: FontWeight.bold,
    ),
  ),
  backgroundColor: Colors.grey[200],
  collapsedBackgroundColor: Colors.white,
  textColor: Colors.blue,
  collapsedTextColor: Colors.black87,
  iconColor: Colors.blue,
  collapsedIconColor: Colors.grey,
  tilePadding: EdgeInsets.symmetric(horizontal: 24.0),
  childrenPadding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0),
  children: [
    Padding(
      padding: EdgeInsets.all(16.0),
      child: Text(
        'This is the expanded content with custom padding and styling.',
        style: TextStyle(fontSize: 16.0),
      ),
    ),
  ],
)

2. Adding Leading and Trailing Widgets

ExpansionTile(
  leading: Icon(Icons.info),
  title: Text('With Leading and Trailing'),
  trailing: Container(
    padding: EdgeInsets.all(4.0),
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.circular(12.0),
    ),
    child: Text(
      '3',
      style: TextStyle(
        color: Colors.white,
        fontSize: 12.0,
      ),
    ),
  ),
  children: [
    ListTile(
      leading: Icon(Icons.star),
      title: Text('Featured Item'),
    ),
    ListTile(
      leading: Icon(Icons.favorite),
      title: Text('Favorite Item'),
    ),
    ListTile(
      leading: Icon(Icons.settings),
      title: Text('Settings Item'),
    ),
  ],
)

Advanced Implementations

1. Nested ExpansionTiles

class NestedExpansionList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ExpansionTile(
      title: Text('Main Category'),
      children: [
        ExpansionTile(
          title: Text('Subcategory 1'),
          children: [
            ListTile(title: Text('Item 1.1')),
            ListTile(title: Text('Item 1.2')),
          ],
        ),
        ExpansionTile(
          title: Text('Subcategory 2'),
          children: [
            ListTile(title: Text('Item 2.1')),
            ListTile(title: Text('Item 2.2')),
          ],
        ),
      ],
    );
  }
}

2. Controlled ExpansionTile

class ControlledExpansionTile extends StatefulWidget {
  @override
  _ControlledExpansionTileState createState() => _ControlledExpansionTileState();
}

class _ControlledExpansionTileState extends State<ControlledExpansionTile> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              _isExpanded = !_isExpanded;
            });
          },
          child: Text(_isExpanded ? 'Collapse' : 'Expand'),
        ),
        ExpansionTile(
          initiallyExpanded: _isExpanded,
          onExpansionChanged: (expanded) {
            setState(() {
              _isExpanded = expanded;
            });
          },
          title: Text('Controlled ExpansionTile'),
          children: [
            ListTile(
              title: Text('Content that can be controlled externally'),
            ),
          ],
        ),
      ],
    );
  }
}

3. Custom Animation

class CustomAnimatedExpansionTile extends StatefulWidget {
  @override
  _CustomAnimatedExpansionTileState createState() =>
      _CustomAnimatedExpansionTileState();
}

class _CustomAnimatedExpansionTileState extends State<CustomAnimatedExpansionTile> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ListTile(
          title: Text('Custom Animated Expansion'),
          trailing: AnimatedRotation(
            duration: Duration(milliseconds: 300),
            turns: _isExpanded ? 0.5 : 0,
            child: Icon(Icons.arrow_drop_down),
          ),
          onTap: () {
            setState(() {
              _isExpanded = !_isExpanded;
            });
          },
        ),
        AnimatedCrossFade(
          duration: Duration(milliseconds: 300),
          firstChild: Container(),
          secondChild: Column(
            children: [
              ListTile(title: Text('Item 1')),
              ListTile(title: Text('Item 2')),
              ListTile(title: Text('Item 3')),
            ],
          ),
          crossFadeState: _isExpanded
              ? CrossFadeState.showSecond
              : CrossFadeState.showFirst,
        ),
      ],
    );
  }
}

Practical Examples

1. FAQ Section

class FAQSection extends StatelessWidget {
  final List<Map<String, String>> faqs = [
    {
      'question': 'What is Flutter?',
      'answer': 'Flutter is Google's UI toolkit for building beautiful, '
          'natively compiled applications for mobile, web, and desktop '
          'from a single codebase.'
    },
    {
      'question': 'Why use Flutter?',
      'answer': 'Flutter allows developers to build beautiful native apps '
          'on iOS and Android from a single codebase. It offers fast '
          'development, expressive UIs, and native performance.'
    },
  ];

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: faqs.length,
      itemBuilder: (context, index) {
        return Card(
          margin: EdgeInsets.all(8.0),
          child: ExpansionTile(
            title: Text(
              faqs[index]['question']!,
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
            children: [
              Padding(
                padding: EdgeInsets.all(16.0),
                child: Text(faqs[index]['answer']!),
              ),
            ],
          ),
        );
      },
    );
  }
}

2. Settings Menu

class SettingsMenu extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        ExpansionTile(
          leading: Icon(Icons.account_circle),
          title: Text('Account Settings'),
          children: [
            ListTile(
              leading: Icon(Icons.person),
              title: Text('Profile'),
              onTap: () {
                // Navigate to profile settings
              },
            ),
            ListTile(
              leading: Icon(Icons.security),
              title: Text('Security'),
              onTap: () {
                // Navigate to security settings
              },
            ),
            ListTile(
              leading: Icon(Icons.notifications),
              title: Text('Notifications'),
              onTap: () {
                // Navigate to notification settings
              },
            ),
          ],
        ),
        ExpansionTile(
          leading: Icon(Icons.settings),
          title: Text('App Settings'),
          children: [
            ListTile(
              leading: Icon(Icons.language),
              title: Text('Language'),
              onTap: () {
                // Open language selection
              },
            ),
            ListTile(
              leading: Icon(Icons.dark_mode),
              title: Text('Theme'),
              onTap: () {
                // Open theme settings
              },
            ),
          ],
        ),
      ],
    );
  }
}

Best Practices

  1. State Management

    • Use controlled expansion when needed
    • Maintain expansion state properly
    • Handle state changes efficiently
  2. Performance

    • Avoid deep nesting of ExpansionTiles
    • Use ListView.builder for large lists
    • Implement lazy loading when necessary
  3. User Experience

    • Provide visual feedback
    • Maintain consistent styling
    • Consider animation duration

Common Issues and Solutions

1. Initial Expansion State

ExpansionTile(
  initiallyExpanded: true, // Set initial state
  maintainState: true, // Keep children in the tree
  title: Text('Always Initially Expanded'),
  children: [
    // Children widgets
  ],
)

2. Custom Expansion Behavior

ExpansionTile(
  onExpansionChanged: (expanded) {
    // Custom behavior when expansion state changes
    if (expanded) {
      // Do something when expanded
    } else {
      // Do something when collapsed
    }
  },
  title: Text('Custom Expansion Behavior'),
  children: [
    // Children widgets
  ],
)

Conclusion

ExpansionTile is a versatile widget that can greatly enhance your app's user interface. Key takeaways:

  • Use ExpansionTile for collapsible sections
  • Customize appearance with built-in properties
  • Implement controlled expansion when needed
  • Consider performance with large lists
  • Follow best practices for state management

By following these guidelines and examples, you can create intuitive and efficient expandable lists in your Flutter applications.