Flexible grid
Learning objectives
- You know how to create scrollable two-dimensional arrays.
- You know how to create a widget that maintains an aspect ratio.
The GridView widget allows creating scrollable two-dimensional arrays. It is often used through the named constructor GridView.count that allows easy generation of a scrollable grid.
The constructor GridView.count
is given two arguments: (1) crossAxisCount
that describes the number of widgets that can be next to each others, and (2) children
that contains the list of widgets that are to be shown in the grid.
For example, the following application would create a grid with two columns and three rows, displaying six letters.
Changing the value given to crossAxisCount
to one would display the letters in a single column with six rows.
class GridScreen extends StatelessWidget {
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 1,
children: const [
Center(child: Text("A")),
Center(child: Text("B")),
Center(child: Text("C")),
Center(child: Text("D")),
Center(child: Text("E")),
Center(child: Text("F")),
],
);
}
}
Having information about the size of the screen or, depending on where used, the size of the parent widget, we can adjust the value given to the crossAxisCount
property. In the following example, we adjust the value based on the width of the screen divided by 150 (rounded down).
class GridScreen extends StatelessWidget {
Widget build(BuildContext context) {
int count = MediaQuery.of(context).size.width ~/ 150;
count = count < 1 ? 1: count;
return GridView.count(
crossAxisCount: count,
children: const [
Center(child: Text("A")),
Center(child: Text("B")),
Center(child: Text("C")),
Center(child: Text("D")),
Center(child: Text("E")),
Center(child: Text("F")),
],
);
}
}
When we try out the above example, we observe that the number of side-by-side widgets changes based on the screen width.
What the ~/?
The ~/
is a truncating division operator, where the result of the division is effectively floored.
Additional arguments that can be given to the GridView.count
named constructor include scrollDirection
(either Axis.vertical
or Axis.horizontal
), mainAxisSpacing
and crossAxisSpacing
(both used to control spacing between widgets), and padding
(for padding the GridView
).
To view the effect of these, we create a ColoredText
widget that shows a given text on an orange background.
class ColoredText extends StatelessWidget {
final String text;
const ColoredText(this.text);
Widget build(BuildContext context) {
return Container(color: Colors.orange, child: Center(child: Text(text)));
}
}
The following application displays the previously shown letters, but this time using the ColoredText
and with adjusted spacing. Padding has been added to separate the containers in the GridView
from each others.
class GridScreen extends StatelessWidget {
Widget build(BuildContext context) {
int count = MediaQuery.of(context).size.width ~/ 150;
count = count < 1 ? 1: count;
return GridView.count(
crossAxisCount: count,
padding: const EdgeInsets.all(10),
mainAxisSpacing: 25,
crossAxisSpacing: 5,
children: const [
ColoredText("A"),
ColoredText("B"),
ColoredText("C"),
ColoredText("D"),
ColoredText("E"),
ColoredText("F"),
],
);
}
}
Opening up the application, we see the orange containers with the texts, as well as the spacing between the containers and the padding around the gridview. Further, when we vary the width of the screen, we notice that the size of the containers shown in the GridView
change to fill the width of the screen.
We notice, however, that the size of the font does not change with the size of the containers. Given that the text widgets are within the containers, we could retrieve information about the parent containers and use that to adjust the used font size. As we know, retrieving information about parent widgets is done using LayoutBuilder
.
The following example would create a situation, where the size of the letters shown in the boxes adjust based on the space available to them.
class ColoredText extends StatelessWidget {
final String text;
const ColoredText(this.text);
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
color: Colors.orange,
child: Center(
child: Text(
text,
textScaleFactor: constraints.maxWidth / 50,
)));
});
}
}
With the GridView
, one of the nice features was that the aspect ratio of the widgets within the GridView
was maintained when the size changed. Let's take a step back and consider a simple application with a container. The following application shows a green container with the height of 100 pixels.
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(home: Scaffold(body: HomeScreen())));
}
class HomeScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Container(color: Colors.green, height: 100);
}
}
As we have set the height to 100 pixels, it is fixed, regardless of the size of the screen. We often wish to define the size of layout widgets and containers relative to the screen size. If we would, for example, want that the container would always take 20% of the available height, we could e.g. use MediaQuery
. In the following example, the height of the widget is relative to the height of the screen.
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(home: Scaffold(body: HomeScreen())));
}
class HomeScreen extends StatelessWidget {
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Container(color: Colors.green, height: 0.2 * height);
}
}
Now, the height of the container is changed when the height of the screen changes. We could, however, also wish to change the height of the container based on the width of the container. Such behavior would be relevant, for example, when showing a picture. Instead of stretching the picture, or just showing a part of it, we would adjust the size of the picture to match the available area.
This is where Flutter's AspectRatio comes to play. With the AspectRatio
widget, we can define the size of a child widget in terms of an aspect ratio -- width / height
-- instead of absolute values. When creating a new AspectRatio
widget, the aspect ratio is as a value to the aspectRatio
argument, while the child widget is given using the argument child
.
As an example, the following application would show a green area that maintains the aspect ratio 2 / 1
, i.e. the width of the green area is always twice the height of the green area.