Expandable Column
A scrollable column widget that supports Expanded and Spacer widgets. Combines the flexibility of Column with the scrolling capability of ScrollView for dynamic, space-aware layouts.
Features
β¨ Use Expanded and Spacer - Unlike ListView, fully supports flex widgets
π± Automatic Scrolling - Scrolls when content overflows the viewport
π― Space Distribution - Children can expand to fill available space
β‘ Performance Optimized - Built on Flutter's efficient sliver system
π¨ Highly Customizable - All standard Column and ScrollView properties supported
Why Use This Package?
The Problem
In Flutter, you often face a dilemma:
ColumnsupportsExpandedandSpacer, but doesn't scroll when content overflowsListViewscrolls perfectly, but doesn't supportExpandedorSpacerwidgetsSingleChildScrollViewwithColumnscrolls, butExpandedwidgets throw errors
The Solution
ExpandableColumn gives you the best of both worlds:
ExpandableColumn(
children: [
Text('Header'),
Expanded(
child: Container(
color: Colors.blue,
child: Center(child: Text('This fills available space')),
),
),
Spacer(),
Text('Footer'),
],
)
Installation
Add this to your pubspec.yaml:
dependencies:
expandable_column: ^0.0.1
Then run:
flutter pub get
Usage
Basic Example
import 'package:flutter/material.dart';
import 'package:flutter_expandable_column/flutter_expandable_column.dart';
class MyScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Expandable Column Example')),
body: ExpandableColumn(
children: [
Container(
height: 100,
color: Colors.red,
child: Center(child: Text('Fixed Height Header')),
),
Expanded(
child: Container(
color: Colors.blue,
child: Center(
child: Text('This expands to fill available space'),
),
),
),
Container(
height: 100,
color: Colors.green,
child: Center(child: Text('Fixed Height Footer')),
),
],
),
);
}
}
Form with Dynamic Content
ExpandableColumn(
padding: EdgeInsets.all(16),
children: [
Text(
'User Registration',
style: Theme.of(context).textTheme.headlineMedium,
),
SizedBox(height: 20),
TextField(decoration: InputDecoration(labelText: 'Name')),
TextField(decoration: InputDecoration(labelText: 'Email')),
TextField(decoration: InputDecoration(labelText: 'Password')),
Spacer(), // Pushes button to bottom
ElevatedButton(
onPressed: () {},
child: Text('Submit'),
),
],
)
Multiple Expanded Widgets
ExpandableColumn(
children: [
Text('Dashboard'),
Expanded(
flex: 2,
child: Container(
color: Colors.amber,
child: Center(child: Text('Takes 2/3 of space')),
),
),
Expanded(
flex: 1,
child: Container(
color: Colors.purple,
child: Center(child: Text('Takes 1/3 of space')),
),
),
Padding(
padding: EdgeInsets.all(16),
child: Text('Footer Text'),
),
],
)
With Custom Scroll Physics
ExpandableColumn(
physics: BouncingScrollPhysics(), // iOS-style bouncing
children: [
// Your widgets here
],
)
Centered Content
ExpandableColumn(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.check_circle, size: 100, color: Colors.green),
SizedBox(height: 20),
Text('Success!', style: TextStyle(fontSize: 24)),
SizedBox(height: 10),
Text('Your action was completed successfully.'),
],
)
API Reference
ExpandableColumn Properties
| Property | Type | Default | Description |
|---|---|---|---|
children |
List<Widget> |
required | The widgets to display vertically |
mainAxisAlignment |
MainAxisAlignment |
start |
How children are aligned vertically |
mainAxisSize |
MainAxisSize |
max |
How much vertical space to occupy |
crossAxisAlignment |
CrossAxisAlignment |
center |
How children are aligned horizontally |
textDirection |
TextDirection? |
null |
Order for horizontal layout |
verticalDirection |
VerticalDirection |
down |
Order for vertical layout |
textBaseline |
TextBaseline? |
null |
Baseline for alignment |
physics |
ScrollPhysics? |
null |
Scroll physics behavior |
primary |
bool? |
null |
Whether this is the primary scroll view |
reverse |
bool |
false |
Whether to reverse scroll direction |
clipBehavior |
Clip |
hardEdge |
How to clip content |
Common Use Cases
1. Login Screen with Logo
ExpandableColumn(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(flex: 2),
Image.asset('assets/logo.png', height: 120),
SizedBox(height: 40),
TextField(decoration: InputDecoration(labelText: 'Username')),
SizedBox(height: 16),
TextField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
SizedBox(height: 24),
ElevatedButton(
onPressed: () {},
child: Text('Login'),
),
Spacer(flex: 3),
],
)
2. Chat-like Interface
ExpandableColumn(
children: [
Expanded(
child: ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) => MessageBubble(messages[index]),
),
),
Divider(height: 1),
MessageInput(),
],
)
3. Settings Page
ExpandableColumn(
children: [
ListTile(
title: Text('Account'),
leading: Icon(Icons.person),
trailing: Icon(Icons.chevron_right),
),
ListTile(
title: Text('Notifications'),
leading: Icon(Icons.notifications),
trailing: Switch(value: true, onChanged: (_) {}),
),
ListTile(
title: Text('Privacy'),
leading: Icon(Icons.lock),
trailing: Icon(Icons.chevron_right),
),
Spacer(),
Padding(
padding: EdgeInsets.all(16),
child: Text(
'Version 1.0.0',
style: TextStyle(color: Colors.grey),
textAlign: TextAlign.center,
),
),
],
)
Comparison with Alternatives
| Feature | ExpandableColumn | Column + SingleChildScrollView | ListView |
|---|---|---|---|
Supports Expanded |
β Yes | β No (throws error) | β No |
Supports Spacer |
β Yes | β No (throws error) | β No |
| Scrollable | β Yes | β Yes | β Yes |
| Fills available space | β Yes | β οΈ Limited | β No |
| Performance | β Optimized | β Good | β Optimized |
Migration Guide
From Column + SingleChildScrollView
Before:
SingleChildScrollView(
child: Column(
children: [
Widget1(),
// Can't use Expanded here!
Widget2(),
],
),
)
After:
ExpandableColumn(
children: [
Widget1(),
Expanded(child: Widget2()), // Now works!
],
)
From Column
Before:
Column(
children: [
Widget1(),
Expanded(child: Widget2()),
],
)
After:
ExpandableColumn(
children: [
Widget1(),
Expanded(child: Widget2()), // Now scrollable too!
],
)
Performance Tips
- Use
constconstructors when possible for better performance - Avoid deeply nested
ExpandableColumnwidgets - use one at the root level - For long lists inside
Expanded, useListView.builderinstead ofColumn - Set
primary: falseif not the primary scroll view in your screen
Troubleshooting
Content not scrolling?
Make sure your total content height exceeds the viewport. If content fits, there's nothing to scroll.
Expanded throwing errors?
Ensure you're using ExpandableColumn directly, not wrapping it in additional widgets that constrain height.
Multiple scroll views conflict?
Set primary: false on nested scroll views:
ExpandableColumn(
primary: false, // Important!
children: [
Expanded(
child: ListView(...),
),
],
)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- π Issues: GitHub Issues
- π¬ Discussions: GitHub Discussions
Changelog
See CHANGELOG.md for a list of changes.
Acknowledgments
- Built with Flutter's powerful sliver system
- Inspired by the common need to combine
Columnflexibility withScrollViewscrolling
Made with β€οΈ for the Flutter community
Libraries
- expandable_column
- expandable flutter column