In this tutorial, we'll learn how to create a selectable grid in Flutter using the GridView.count widget. We'll also be using a global function to change the current selected item, making this widget useful for a settings page or anywhere you need to select from a grid of items. To demonstrate, we'll be using a list of months as our example.
Before we begin, check out the accompanying video for this post:
If you like the content, consider following for more and subscribing to my YouTube channel ramgendeploy 😁
First, let's start with a list of maps for the grid list:
const List<Map<String, dynamic>> months = <Map<String, dynamic>>[
<String, dynamic>{
'month': 'January',
'img': 'assets/images/Jan.jpg',
},
<String, dynamic>{
'month': 'February',
'img': 'assets/images/Feb.jpg',
},
<String, dynamic>{
'month': 'March',
'img': 'assets/images/Mar.jpg',
},
];
In the main widget where the grid list is going to be, we have the following in the state:
The optionSelected
variable is used to select the current item. We then have a function that we set to the children of the grid to select the current id.
int optionSelected = 0;
void checkOption(int index) {
setState(() {
optionSelected = index;
});
}
To render the grid view, we use the GridView.count
widget. This creates a GridView from a list, and we need to specify the number of columns with the crossAxisCount
property. We then populate the list with a for loop going through the months array, giving the months array a title and image.
The important part here is the onTap
function. We have a lambda function that runs the checkOption
function with the corresponding id of the selected item. This id must match the id in the selected flag. If it does, we set the flag to true.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Selection and Settings $optionSelected'),
),
body: GridView.count(
crossAxisCount: 2,
children: <Widget>[
for (int i = 0; i < months.length; i++)
MonthOption(
months[i]['month'] as String,
img: months[i]['img'] as String,
onTap: () => checkOption(i + 1),
selected: i + 1 == optionSelected,
)
],
),
);
}
Now let's take a look at the statelessWidget for the options of the grid:
class MonthOption extends StatelessWidget {
const MonthOption(
this.title, {
Key key,
this.img,
this.onTap,
this.selected,
}) : super(key: key);
final String title;
final String img;
final VoidCallback onTap;
final bool selected;
We have the title, an img string for the asset image, a VoidCallback for the onTap function, and the selected flag.
In the build method, we have:
Widget build(BuildContext context) {
return Ink.image(
fit: BoxFit.cover,
image: AssetImage(img),
child: InkWell(
onTap: onTap,
child: Align(
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: selected ?? false ? Colors.red : Colors.transparent,
width: selected ?? false ? 5 : 0,
),
),
),
padding: const EdgeInsets.all(8.0),
child: Row(children: <Widget>[
AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: selected ?? false
? Colors.blue.withOpacity(0.8)
: Colors.black54,
borderRadius: BorderRadius.circular(10),
),
child: Text(
title ?? '',
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 16),
),
),
]),
),
),
),
);
}
The tree structure is as follows:
Ink.image -> InkWell -> Align -> AnimatedContainer -> Row -> AnimatedContainer
Animated containers are great for this kind of thing. In the first one, we change the color and width of the border side. Then in the second, we change the background color of the title.
In some parts of the code, we use the null-aware operator to check if selected is null. If it is, we default to false. If it's not null, we use its value. We also use this in the title in the Text widget because we can't give a null value to the Text widget.
You may be wondering how this performs. Doesn't all the widget get re-rendered when we hit one option? What if we have hundreds of options? I tested this with 200 items and it worked really well. Flutter is great with this kind of stuff. You may have problems if you have a lot going on in the app, but for a settings section, this is an excellent solution.
Top comments (1)
@ramgendeploy thnk you for posting this.
I have written a similar article on my blog. But that pulls the data from a REST API and covers more practicle senarios. stacktips.com/articles/how-to-buil...
Demo
https://www.youtube.com/watch?v=bisopNeraJs&list=PLjOeTkAnaMPeX1eIMMtj5m02p_zer9pwP&ab_channel=TheTechMojo