Introduction
Flutter GridView
is almost the same as ListView
except it provides a 2D view comparison to ListView
single direction view. It is also quite a popular widget across mobile app development. If you don't believe me, open any ecommerce app in your mobile - it is definitely dependent either on ListView
or GridView
to display data, for e.g.:
Here, Amazon mobile app displays data in grid:
Another one, PayTM, one of popular online wallet service app in India, widely used grid layout to display different products:
Background
The ultimate aim of this article to achieve a view similar to this:
However, if you have noticed the above image, that is in landscape mode. So here is what I am going to do in this article. When the application is in portrait mode, MobileApp would display item in ListView
and when it’s in landscape mode, show items in 3 items per row grid. I also explore creating custom widget, by moving gridview
implementation in a separate class.
Using the Code
I am going to build on my previous article Flutter Getting Started: Tutorial 4 ListView, there I have already created ListView
based application, here the initial project structure and initial UI.
Here is the initial code from which we start building on:
class HomePage extends StatelessWidget {
final List<City> _allCities = City.allCities();
HomePage() {}
final GlobalKey scaffoldKey = new GlobalKey();
@override
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
title: new Text(
"Cites around world",
style: new TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
color: Colors.black87),
),
),
body: new Padding(
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
child: getHomePageBody(context)));
}
getHomePageBody(BuildContext context) {
return ListView.builder(
itemCount: _allCities.length,
itemBuilder: _getListItemUI,
padding: EdgeInsets.all(0.0),
);
}
Widget _getListItemUI(BuildContext context, int index,
{double imgwidth: 100.0}) {
return new Card(
child: new Column(
children: <Widget>[
new ListTile(
leading: new Image.asset(
"assets/" + _allCities[index].image,
fit: BoxFit.fitHeight,
width: imgwidth,
),
title: new Text(
_allCities[index].name,
style: new TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
),
subtitle: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(_allCities[index].country,
style: new TextStyle(
fontSize: 13.0, fontWeight: FontWeight.normal)),
new Text('Population: ${_allCities[index].population}',
style: new TextStyle(
fontSize: 11.0, fontWeight: FontWeight.normal)),
]),
onTap: () {
_showSnackBar(context, _allCities[index]);
},
)
],
));
}
_showSnackBar(BuildContext context, City item) {
final SnackBar objSnackbar = new SnackBar(
content: new Text("${item.name} is a city in ${item.country}"),
backgroundColor: Colors.amber,
);
Scaffold.of(context).showSnackBar(objSnackbar);
}
}
Before I start on the actual quest, let me give you a brief of what I have done above:
- I have created a simple
ListView
using ListView.builder
, which gives flexibility of creating infinite listitem
view, as it calls callback function only for those items, which could be displayed on the screen. - I am displaying
City
information like City Landmark image, followed by CityName
, Country
the city belongs and her population. - Lastly on clicking, it shows a small self disappearing message at the bottom of the screen, known as
SnackBar
.
Now from here, our work starts, as I mentioned earlier, we would be refactoring a new widget into a different class, to keep our code modular and increase clarity of code. So create a new folder widget
under lib folder and add a new DART file mygridview.dart.
Once you add the file, first import material widget, by importing 'package:flutter/material.dart'
and then add MyGridView
class which extends our favourite StatelessWidget
and override Build
function, so bare bone code looks like this:
import 'package:flutter/material.dart';
import 'package:flutter5_gridlist/model/city.dart';
class MyGridView extends StatelessWidget {
final List<City> allCities;
MyGridView({Key key, this.allCities}) : super(key: key);
@override
Widget build(BuildContext context) {
return null;
}
}
I would now add basic GridView
just displaying the City
name, so I will add the following code in overridden Build
function:
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 3,
padding: EdgeInsets.all(16.0),
childAspectRatio: 8.0,
children: _getGridViewItems(context),
);
}
_getGridViewItems(BuildContext context){
List<Widget> allWidgets = new List<Widget>();
for (int i = 0; i < allCities.length; i++) {
var widget = new Text(allCities[i].name);
allWidgets.add(widget);
};
return allWidgets;
}
Explanation of the above code:
GridView.count
method would provide GridView
widget for application crossAxisCount
property is used to let mobile application know how many items we want to show in each row children
property will contain all the widgets you wish to show when page is loaded childAspectRatio
, which is the ratio of the cross-axis to the main-axis extent of each child, since I am displaying the name, I will keep the same at 8.0 so that margin between two tiles is reduced
Here is what the UI looks like:
Now let's make the UI similar to what we are seeing in the ListView
. Here, I created a new function which will send City
class in the form of Card
.
_getGridItemUI(BuildContext context, City item) {
return new InkWell(
onTap: () {
_showSnackBar(context, item);
},
child: new Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Image.asset(
"assets/" + item.image,
fit: BoxFit.fill,
),
new Expanded(
child: new Center(
child: new Column(
children: <Widget>[
new SizedBox(height: 8.0),
new Text(
item.name,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
new Text(item.country),
new Text('Population: ${item.population}')
],
)))
],
),
elevation: 2.0,
margin: EdgeInsets.all(5.0),
));
}
Explanation of the above code:
- I am using
Inkwell
class, since Card
class doesn't support gesture directly, So I wrapped it inside InkWell
class, to utilize its onTap
event for displaying SnackBar
. - Rest of the code is similar to
ListView
card except no width is specified. - Also, since we are displaying a complete card, don't forget to change
childAspectRatio
from 8.0 to 8.0/9.0, as we require more height .
Before I forget, I told at the start of the application, I will show ListView
in portrait orientation and GridView
in landscape orientation, so for achieving it MediaQuery
class to identify the orientation. Whenever the orientation is changed, i.e., you tilt your mobile, the widget are redrawn, so Build
function is called, there you could take a decision what code should be called. So in homepage.dart
class, we will use the following function to handle Orientation changes.
getHomePageBody(BuildContext context) {
if (MediaQuery.of(context).orientation == Orientation.portrait)
return ListView.builder(
itemCount: _allCities.length,
itemBuilder: _getListItemUI,
padding: EdgeInsets.all(0.0),
);
else
return new MyGridView(allCities: _allCities);
}
So our final UI would be like this:
End of tutorial.
Points of Interest
Please go through these articles. It might give you a headway, where the wind is actually flowing:
- https://material.io/design/components/cards.html
- Github: https://github.com/thatsalok/FlutterExample/tree/master/flutter5_gridlist
Flutter Tutorials
- Flutter Getting Started: Tutorial 1 Basics
- Flutter Getting Started: Tutorial 2 - StateFulWidget
- Flutter Getting Started: Tutorial 3 Navigation
- Flutter Getting Started: Tutorial 4 ListView
Dart Tutorials
- DART2 Prima Plus - Tutorial 1
- DART2 Prima Plus - Tutorial 2 - LIST
- DART2 Prima Plus - Tutorial 3 - MAP
History
- 22-July-2018: First version