DEV Community

loading...
Cover image for Making a Selectable Grid in flutter

Making a Selectable Grid in flutter

ramgendeploy profile image Ramiro Originally published at ramagg.com ・3 min read

Check out the video for this posts ->

Let's see how to make a selectable grid in Flutter, we are going to use gridview.count and using a global function to change the current selected item, this widget can be useful in a settings page, or well anywhere you need to select from a grid of items, in this example we are going to select months also it has a couple of animations so the user can see that the item is being selected

Alright we 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',
  },
 ];
Enter fullscreen mode Exit fullscreen mode

Then in the main widget where the grid list is going to be we have this in the state

  int optionSelected = 0;

  void checkOption(int index) {
    setState(() {
      optionSelected = index;
    });
  }
Enter fullscreen mode Exit fullscreen mode

optionSelected is used to select the current item, then we have the function that we are going to set to the children of the grid to select the current id.

Then we have this code in to render the grid view lets see what we have here.

  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,
            )
        ],
      ),
    );
  }
Enter fullscreen mode Exit fullscreen mode

We are using the GridView.count widget this creates a GridView from a list, we need to specify the number of columns with the crossAxisCount then we populate the list with a for loop going through the months array, so we give the months array title, the image.

The important part here is the onTap function we have a lambda function that runs the checkOption with the corresponding id of the selected item, this has to be the same id in the selected flag, so we set the flag to true if the selected id is equal to the corresponding id.

Now let's see 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;

Enter fullscreen mode Exit fullscreen mode

So we have the title, and an img string for the asset image, a VoidCallback for the onTap function, and the selected flag.

Now in the build 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),
                ),
              ),
            ]),
          ),
        ),
      ),
    );
  }
Enter fullscreen mode Exit fullscreen mode

So the tree is like this

Ink.image -> InkWell -> Align -> AnimatedContainer -> Row -> AnimatedContainer

Animated containers are awesome for this kind of thing in the first one we change, the color and with of the border side, then in the second, we change the background color of the title.

Here in some parts we use the null-aware operator to check if selected is null and 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 to the Text widget.

Maybe you wonder how this performs because when we hit one option doesn't all the widget gets re-rendered what if we have hundreds of options well I tested this with 200 items and it worked really well flutter is really good with this kind of stuff, maybe you'll have problems if you have a lot going on in the app but for a settings section I think this is awesome.

Discussion (1)

pic
Editor guide