Skip to main content

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

ParameterTypeDescription
itemsList<T>Required. List of items to display in the grid
selectedItemsList<T>Required. Currently selected items
onSelectionChangedValueChanged<List<T>>Required. Callback when selection changes
itemBuilderWidget Function(BuildContext, T, bool)Required. Builder function for grid items
crossAxisCountint?Number of columns in the grid (default: 2)
mainAxisSpacingdouble?Spacing between rows (default: 8.0)
crossAxisSpacingdouble?Spacing between columns (default: 8.0)
childAspectRatiodouble?Aspect ratio of grid items (default: 1.0)
multiSelectboolWhether multiple selection is allowed (default: true)
paddingEdgeInsets?Padding around the grid
physicsScrollPhysics?Scroll physics for the grid
shrinkWrapboolWhether 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