ListAppi
A flexible and performant list widget that provides customizable scrolling behavior and layout options for displaying dynamic content.
Features
- Custom Item Builder: Build items dynamically with a function-based approach
- Scroll Control: Full control over scrolling behavior and physics
- Direction Support: Horizontal and vertical scrolling modes
- Empty State: Built-in empty state handling with customizable text
- Performance Optimized: Uses SliverList for efficient rendering
- Flexible Layout: Supports shrink wrapping and custom padding
- RTL Support: Right-to-left language support
Usage
Basic List
ListAppi(
count: items.length,
child: (index) => ListTile(
title: Text('Item ${index + 1}'),
subtitle: Text('Description for item ${index + 1}'),
),
)
Horizontal List
ListAppi(
count: categories.length,
horizondal: true,
padding: EdgeInsets.symmetric(horizontal: 16),
child: (index) => Container(
width: 120,
margin: EdgeInsets.only(right: 12),
child: Card(
child: Center(
child: Text(categories[index]),
),
),
),
)
List with Custom Scroll Controller
class MyListWidget extends StatefulWidget {
_MyListWidgetState createState() => _MyListWidgetState();
}
class _MyListWidgetState extends State<MyListWidget> {
final ScrollController _scrollController = ScrollController();
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
_scrollController.animateTo(
0,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Text('Scroll to Top'),
),
Expanded(
child: ListAppi(
count: 100,
controller: _scrollController,
child: (index) => Card(
margin: EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text('Item ${index + 1}'),
subtitle: Text('This is item number ${index + 1}'),
),
),
),
),
],
);
}
}
Parameters
Name | Type | Description | Default |
---|---|---|---|
child | Function(int index) | Required. Builder function that returns a widget for each index | - |
count | int | Required. Number of items in the list | - |
controller | ScrollController? | Optional scroll controller for programmatic scrolling | null |
reversed | bool? | Whether to reverse the scroll direction | false |
shrink | bool? | Whether the list should shrink-wrap its content | false |
physics | ScrollPhysics? | The scroll physics to use | null |
primary | bool? | Whether this is the primary scroll view | null |
padding | EdgeInsetsGeometry? | Padding around the list content | EdgeInsets.zero |
horizondal | bool? | Whether to scroll horizontally instead of vertically | false |
defaultText | String? | Text to show when the list is empty | 'Empty' |
Behavior
Dynamic Content
- The
child
function is called for each index from 0 tocount - 1
- Items are built on-demand for optimal performance
- Supports any widget type as list items
Scroll Direction
- Vertical (default): Items scroll up and down
- Horizontal: Items scroll left and right
- Direction is controlled by the
horizondal
parameter
Empty State
- When
count
is 0, displays thedefaultText
message - Empty state is centered and styled consistently
- Customizable through the
defaultText
parameter
Best Practices
- Performance: Use the builder pattern for large lists to ensure optimal performance
- Controllers: Use scroll controllers when you need programmatic scroll control
- Padding: Apply appropriate padding for visual spacing
- Empty States: Provide meaningful empty state messages
- Item Keys: Consider using keys for list items when order might change
- Memory: The widget automatically handles memory management for large lists
Examples
Product List
ListAppi(
count: products.length,
padding: EdgeInsets.all(16),
child: (index) {
final product = products[index];
return Card(
margin: EdgeInsets.only(bottom: 12),
child: ListTile(
leading: Image.network(
product.imageUrl,
width: 50,
height: 50,
fit: BoxFit.cover,
),
title: Text(product.name),
subtitle: Text('\$${product.price}'),
trailing: IconButton(
icon: Icon(Icons.add_shopping_cart),
onPressed: () => _addToCart(product),
),
),
);
},
)
Chat Messages
ListAppi(
count: messages.length,
reversed: true, // Show newest messages at bottom
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: (index) {
final message = messages[index];
return Align(
alignment: message.isMe
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
margin: EdgeInsets.only(bottom: 8),
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: message.isMe ? Colors.blue : Colors.grey[300],
borderRadius: BorderRadius.circular(16),
),
child: Text(
message.text,
style: TextStyle(
color: message.isMe ? Colors.white : Colors.black,
),
),
),
);
},
)
Category Chips (Horizontal)
ListAppi(
count: categories.length,
horizondal: true,
shrink: true,
padding: EdgeInsets.symmetric(horizontal: 16),
child: (index) {
final category = categories[index];
final isSelected = selectedCategory == category;
return Container(
margin: EdgeInsets.only(right: 8),
child: FilterChip(
label: Text(category),
selected: isSelected,
onSelected: (selected) {
setState(() => selectedCategory = category);
},
),
);
},
)
Settings List
ListAppi(
count: settingsItems.length,
child: (index) {
final item = settingsItems[index];
return ListTile(
leading: Icon(item.icon),
title: Text(item.title),
subtitle: item.subtitle != null ? Text(item.subtitle!) : null,
trailing: item.hasSwitch
? Switch(
value: item.isEnabled,
onChanged: (value) => _toggleSetting(item, value),
)
: Icon(Icons.chevron_right),
onTap: item.hasSwitch ? null : () => _navigateToSetting(item),
);
},
)
Infinite Scroll List
class InfiniteListWidget extends StatefulWidget {
_InfiniteListWidgetState createState() => _InfiniteListWidgetState();
}
class _InfiniteListWidgetState extends State<InfiniteListWidget> {
final ScrollController _controller = ScrollController();
List<String> items = List.generate(20, (index) => 'Item $index');
bool isLoading = false;
void initState() {
super.initState();
_controller.addListener(_scrollListener);
}
void _scrollListener() {
if (_controller.position.pixels == _controller.position.maxScrollExtent) {
_loadMoreItems();
}
}
void _loadMoreItems() async {
if (isLoading) return;
setState(() => isLoading = true);
// Simulate network delay
await Future.delayed(Duration(seconds: 1));
setState(() {
items.addAll(List.generate(10, (index) => 'Item ${items.length + index}'));
isLoading = false;
});
}
Widget build(BuildContext context) {
return ListAppi(
count: items.length + (isLoading ? 1 : 0),
controller: _controller,
child: (index) {
if (index == items.length) {
return Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
);
}
return ListTile(
title: Text(items[index]),
subtitle: Text('Description for ${items[index]}'),
);
},
);
}
}
See Also
- TableAppi - For tabular data display
- SearchChipAppi - For searchable chip lists
- SelectorListAppi - For horizontal selection lists