SelectorGridAppi
A customizable grid-based selector widget for displaying and selecting items in a responsive grid layout.
Features
- Responsive Grid: Automatically adjusts to screen size with configurable columns
- Multi-Selection: Support for single or multiple item selection
- Custom Styling: Configurable item appearance and selection states
- Flexible Content: Support for any widget type as grid items
- Selection Callbacks: Easy handling of selection changes
- Accessibility: Built-in accessibility support
Usage
List<String> selectedItems = [];
SelectorGridAppi<String>(
items: ['Option 1', 'Option 2', 'Option 3', 'Option 4'],
selectedItems: selectedItems,
onSelectionChanged: (List<String> selected) {
setState(() {
selectedItems = selected;
});
},
itemBuilder: (context, item, isSelected) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: isSelected ? Colors.blue : Colors.grey[200],
borderRadius: BorderRadius.circular(8),
),
child: Text(item),
);
},
)
Parameters
Parameter | Type | Description |
---|---|---|
items | List<T> | Required. List of items to display in the grid |
selectedItems | List<T> | Required. Currently selected items |
onSelectionChanged | ValueChanged<List<T>> | Required. Callback when selection changes |
itemBuilder | Widget Function(BuildContext, T, bool) | Required. Builder function for grid items |
crossAxisCount | int? | Number of columns in the grid (default: 2) |
mainAxisSpacing | double? | Spacing between rows (default: 8.0) |
crossAxisSpacing | double? | Spacing between columns (default: 8.0) |
childAspectRatio | double? | Aspect ratio of grid items (default: 1.0) |
multiSelect | bool | Whether multiple selection is allowed (default: true) |
padding | EdgeInsets? | Padding around the grid |
physics | ScrollPhysics? | Scroll physics for the grid |
shrinkWrap | bool | Whether the grid should shrink wrap its content (default: false) |
Examples
Basic String Selector
List<String> selectedColors = [];
List<String> colors = ['Red', 'Blue', 'Green', 'Yellow', 'Purple', 'Orange'];
SelectorGridAppi<String>(
items: colors,
selectedItems: selectedColors,
onSelectionChanged: (List<String> selected) {
setState(() {
selectedColors = selected;
});
},
crossAxisCount: 3,
itemBuilder: (context, color, isSelected) {
return Container(
decoration: BoxDecoration(
color: isSelected ? Colors.blue[100] : Colors.grey[200],
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected ? Colors.blue : Colors.grey,
width: 2,
),
),
child: Center(
child: Text(
color,
style: TextStyle(
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
);
},
)
Single Selection Mode
List<int> selectedNumber = [];
List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
SelectorGridAppi<int>(
items: numbers,
selectedItems: selectedNumber,
multiSelect: false, // Single selection only
onSelectionChanged: (List<int> selected) {
setState(() {
selectedNumber = selected;
});
},
crossAxisCount: 3,
itemBuilder: (context, number, isSelected) {
return Container(
decoration: BoxDecoration(
color: isSelected ? Colors.green : Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Center(
child: Text(
'$number',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: isSelected ? Colors.white : Colors.black,
),
),
),
);
},
)
Custom Object Selector
class Category {
final String id;
final String name;
final IconData icon;
Category(this.id, this.name, this.icon);
}
List<Category> selectedCategories = [];
List<Category> categories = [
Category('1', 'Food', Icons.restaurant),
Category('2', 'Travel', Icons.flight),
Category('3', 'Shopping', Icons.shopping_bag),
Category('4', 'Entertainment', Icons.movie),
];
SelectorGridAppi<Category>(
items: categories,
selectedItems: selectedCategories,
onSelectionChanged: (List<Category> selected) {
setState(() {
selectedCategories = selected;
});
},
crossAxisCount: 2,
childAspectRatio: 1.2,
itemBuilder: (context, category, isSelected) {
return Card(
elevation: isSelected ? 8 : 2,
color: isSelected ? Colors.blue[50] : Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
category.icon,
size: 32,
color: isSelected ? Colors.blue : Colors.grey[600],
),
SizedBox(height: 8),
Text(
category.name,
style: TextStyle(
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? Colors.blue : Colors.black,
),
),
],
),
);
},
)
Responsive Grid with Custom Spacing
SelectorGridAppi<String>(
items: items,
selectedItems: selectedItems,
onSelectionChanged: (selected) => setState(() => selectedItems = selected),
crossAxisCount: MediaQuery.of(context).size.width > 600 ? 4 : 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
padding: EdgeInsets.all(16),
itemBuilder: (context, item, isSelected) {
return AnimatedContainer(
duration: Duration(milliseconds: 200),
decoration: BoxDecoration(
color: isSelected ? Colors.blue : Colors.grey[100],
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected ? Colors.blue : Colors.transparent,
width: 2,
),
),
child: Center(child: Text(item)),
);
},
)
Best Practices
- Use appropriate
crossAxisCount
based on screen size and content - Provide clear visual feedback for selected vs unselected states
- Consider using
AnimatedContainer
for smooth selection transitions - Set appropriate
childAspectRatio
based on your content requirements - Use
multiSelect: false
when only one selection should be allowed - Implement proper equality comparison for custom objects
- Consider accessibility by providing semantic labels and sufficient contrast