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
-
State Management
- Use controlled expansion when needed
- Maintain expansion state properly
- Handle state changes efficiently
-
Performance
- Avoid deep nesting of ExpansionTiles
- Use ListView.builder for large lists
- Implement lazy loading when necessary
-
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.