Layouts #
What are layout widgets #
Some widgets in Flutter don’t show anything on the screen on their own. They are there to control the layout of other widgets.
You have seen a couple of these already like Center
and Column
.
However, there are many more.
Layout widgets can be divided into two categories, those that have just a single child widget, and those that have multiple.
This exercise will show some of the layout widgets that are most commonly used. You can find a full list of layout widgets here.
Single child layout widgets #
Center #
You have already seen the Center widget. It simply puts its child in the center of the available space.
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Text("Hello World"),
),
),
),
);
}
Container #
The Container widget wraps just a single child. It provides a number of ways to alter the appearance of the child. Including margin, padding, decoration, color, shape and size.
Here is an example you can play around with:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Container(
margin: const EdgeInsets.all(50),
padding: const EdgeInsets.all(10),
width: 150,
height: 75,
decoration: const BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.all(Radius.circular(20)),
gradient: LinearGradient(
colors: [
Colors.lightBlue,
Colors.lightGreen,
],
begin: Alignment(0, 0),
end: Alignment(0.5, 0.5),
),
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 5,
offset: Offset(5, 5),
)
],
),
child: const Text("Hello World"),
),
),
),
);
}
You can also set the alignment of the child:
Container(
alignment: Alignment.bottomCenter,
child: const Text("Hello World"),
)
But then height and width will be ignored.
That was a lot of stuff.
But don’t worry, you don’t have to memorize it all.
Your IDE will be able to guide you.
You just need to remember that if you need something that you can style, like a
<div></div>
in HTML, then a Container
is likely what you are looking for.
SizedBox #
Often you need to just be able to set the size of something. For that a SizedBox is probably a better fit.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: SizedBox(
height: 75,
width: double.infinity,
child: Container(color: Colors.teal),
),
),
),
);
}
ConstrainedBox #
Maybe you want to set a minimum or maximum size instead. For that there is ConstrainedBox.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400,
),
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(colors: [Colors.blue, Colors.amber, Colors.pink])),
),
),
),
),
);
}
Note: Drag the border between code and rendered app to change its size.
Multi child layout widgets #
The following widgets takes a list of children.
Row and Column #
Row and Column you will be using a lot as building blocks for your layouts. The difference between them is what axis it uses to layout its children.
Row
will layout its children horizontally.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Row(
children: [
Container(width: 50, height: 50, color: Colors.amber),
Container(width: 50, height: 50, color: Colors.blue),
Container(width: 50, height: 50, color: Colors.brown),
Container(width: 50, height: 50, color: Colors.cyan),
],
),
),
),
);
}
And Column
will layout vertically.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Column(
children: [
Container(width: 50, height: 50, color: Colors.amber),
Container(width: 50, height: 50, color: Colors.blue),
Container(width: 50, height: 50, color: Colors.brown),
Container(width: 50, height: 50, color: Colors.cyan),
],
),
),
),
);
}
Row
and Column
are often combined to create more intricate layouts.
They also both have a mainAxisAlignment
parameter to control the spacing of its children.
The different possibilities are shown below:
import 'package:flutter/material.dart';
void main() {
final boxes = [
Container(width: 30, height: 30, color: Colors.amber),
Container(width: 30, height: 30, color: Colors.blue),
Container(width: 30, height: 30, color: Colors.brown),
Container(width: 30, height: 30, color: Colors.cyan),
];
runApp(
MaterialApp(
home: Scaffold(
body: Column(
children: [
const Text("MainAxisAlignment.start"),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: boxes,
),
const SizedBox(height: 50),
const Text("MainAxisAlignment.end"),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: boxes,
),
const SizedBox(height: 50),
const Text("MainAxisAlignment.center"),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: boxes,
),
const SizedBox(height: 50),
const Text("MainAxisAlignment.spaceAround"),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: boxes,
),
const SizedBox(height: 50),
const Text("MainAxisAlignment.spaceBetween"),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: boxes,
),
const SizedBox(height: 50),
const Text("MainAxisAlignment.spaceEvenly"),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: boxes,
),
],
),
),
),
);
}
If you don’t want empty space around the children, then you can wrap children in Expanded widget. It makes the child widget expand to fill out the available space.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Row(
children: [
Container(width: 30, height: 30, color: Colors.amber),
Expanded(
child: Container(width: 30, height: 30, color: Colors.blue),
),
Container(width: 30, height: 30, color: Colors.brown),
],
),
),
),
);
}
If you wrap all the children in Expanded
then they will fill out the space by
dividing it between them.
You can control how much space (relative to the others) a Expanded
gets by
setting the flex value.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Row(
children: [
Expanded(
child: Container(width: 30, height: 30, color: Colors.amber),
),
Expanded(
flex: 2,
child: Container(width: 30, height: 30, color: Colors.blue),
),
Expanded(
flex: 3,
child: Container(width: 30, height: 30, color: Colors.brown),
),
],
),
),
),
);
}
Expanded
is allocated space on the fraction of its flex value out of the sum
of all flex values.
It has a default flex value of 1.
In the code above the sum of flex values is 6. So, the first child gets $6/1$ of the space. Second gets $6/2$ and third gets $6/3$ (or half).
Wrap #
Sometimes you might want more children in a Row
or Column
than what fits on the screen.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Row(
children: [
Container(width: 50, height: 50, color: Colors.amber),
Container(width: 50, height: 50, color: Colors.blue),
Container(width: 50, height: 50, color: Colors.brown),
Container(width: 50, height: 50, color: Colors.cyan),
Container(width: 50, height: 50, color: Colors.green),
Container(width: 50, height: 50, color: Colors.indigo),
Container(width: 50, height: 50, color: Colors.lime),
],
),
),
),
);
}
In which case you will see diagonal stripes alternating between yellow and black. Like this:
That is something you should try to avoid.
One solution is to use the
Wrap widget.
It will arrange its children like Row
or Column
, but when it runs out of
space it will wrap to a new line.
Let’s try the example above again, but with a Wrap
instead of a Row
.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Wrap(
children: [
Container(width: 50, height: 50, color: Colors.amber),
Container(width: 50, height: 50, color: Colors.blue),
Container(width: 50, height: 50, color: Colors.brown),
Container(width: 50, height: 50, color: Colors.cyan),
Container(width: 50, height: 50, color: Colors.green),
Container(width: 50, height: 50, color: Colors.indigo),
Container(width: 50, height: 50, color: Colors.lime),
],
),
),
),
);
}
Drag the border between code and layout and observe how the arrangement changes.
Table #
The Table widget is sometimes a better option than combining multiple Row and Column widgets.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Table(
border: TableBorder.all(),
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
TableRow(
decoration: const BoxDecoration(color: Colors.grey),
children: [
const SizedBox(height: 64),
Container(height: 64, color: Colors.green),
Container(height: 32, color: Colors.yellow),
],
),
TableRow(
decoration: const BoxDecoration(color: Colors.grey),
children: [
const SizedBox(height: 64),
TableCell(
verticalAlignment: TableCellVerticalAlignment.top,
child: Container(height: 32, width: 32, color: Colors.red),
),
TableCell(
verticalAlignment: TableCellVerticalAlignment.bottom,
child: Container(height: 32, color: Colors.purple),
),
],
),
],
),
),
),
);
}
A Table
can have a default alignment for its children, which can be overridden
by wrapping the child in a
TableCell.
Stack #
The Stack widget is used to layer its children on top of each other. Think of it as a stack of paper on your desk view from the top.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Stack(
children: [
Container(
width: 100,
height: 100,
color: Colors.red,
),
Container(
width: 90,
height: 90,
color: Colors.green,
),
Container(
width: 80,
height: 80,
color: Colors.blue,
),
],
),
),
),
);
}
The alignment
property can be used to change the default position for
children.
It can be overridden on a per child basis by wrapping it in the
Positioned
widget.
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Stack(
alignment: Alignment.center,
children: [
CircleAvatar(radius: 100, child: Text("HBO")),
Positioned(
bottom: 15,
child: Row(
children: [
Icon(Icons.add_a_photo),
Icon(Icons.favorite),
Icon(Icons.add_comment)
],
),
)
],
),
),
),
),
);
}
ListView #
The ListView widget is used to create scrollable lists.
A ListTile is
often for the children ListView
, since it provides an easy way to layout the
content.
However, the children can be any widget as long as their size is constrained in
the scroll direction.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: ListView(
children: const [
ListTile(
leading: CircleAvatar(child: Text('A')),
title: Text('Headline'),
subtitle: Text('Supporting text'),
trailing: Icon(Icons.favorite_rounded),
),
Divider(height: 0),
ListTile(
leading: CircleAvatar(child: Text('B')),
title: Text('Headline'),
subtitle: Text(
'Longer supporting text to demonstrate how the text wraps and how the leading and trailing widgets are centered vertically with the text.'),
trailing: Icon(Icons.favorite_rounded),
),
Divider(height: 0),
ListTile(
leading: CircleAvatar(child: Text('C')),
title: Text('Headline'),
subtitle: Text(
"Longer supporting text to demonstrate how the text wraps and how setting 'ListTile.isThreeLine = true' aligns leading and trailing widgets to the top vertically with the text."),
trailing: Icon(Icons.favorite_rounded),
isThreeLine: true,
),
Divider(height: 0),
ListTile(
leading: CircleAvatar(child: Text('D')),
title: Text('Headline'),
subtitle: Text('Another tile to demonstrate scrolling.'),
trailing: Icon(Icons.favorite_rounded),
),
Divider(height: 0),
ListTile(
leading: CircleAvatar(child: Text('E')),
title: Text('Headline'),
subtitle: Text('Yet another tile to demonstrate scrolling.'),
trailing: Icon(Icons.favorite_rounded),
),
Divider(height: 0),
],
),
),
),
);
}
Sometimes the items you want to display in a ListView need to be generated or
fetched.
In such situations you would use a itemBuilder
function to build the items.
import 'package:flutter/material.dart';
final List<int> colorCodes = [800, 600, 300, 100];
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) => Container(
height: 50,
color: Colors.blue[colorCodes[index % colorCodes.length]],
child: Center(child: Text('Entry $index')),
),
),
),
),
);
}
GridView #
If want a scrollable grid then GridView is the widget to use.
Among other things, it is a perfect fit for creating a gallery or product catalog.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: GridView.count(
padding: const EdgeInsets.all(20),
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: 2,
children: List.generate(20, (i) =>
Container(
padding: const EdgeInsets.all(8),
color: Colors.lightBlue[100],
child: FlutterLogo(style: FlutterLogoStyle.values[i % FlutterLogoStyle.values.length]),
),
)
),
),
),
);
}
The spacing between tiles can be controlled with mainAxisSpacing
and
crossAxisSpacing
.
The number of tiles displayed across is controlled with crossAxisCount
.
Try to change crossAxisCount
.
You can use the GridTile widget in children list to quickly add some flair.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: GridView.count(
padding: const EdgeInsets.all(20),
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: 1,
children: [
GridTile(
header: const GridTileBar(
title: Text("T-Shirt"),
subtitle: Text("Basic shirt made of cotton"),
),
footer: const GridTileBar(
leading: Text("19.99\$"),
),
child: Container(
color: Colors.lightBlue,
child: const Placeholder(),
),
),
GridTile(
header: const GridTileBar(
title: Text("Premium t-Shirt"),
subtitle: Text("Luxury shirt made of silk"),
),
footer: const GridTileBar(
leading: Text("300.00\$"),
),
child: Container(
color: Colors.lightBlue,
child: const Placeholder(),
),
),
GridTile(
header: const GridTileBar(
title: Text("Jeans"),
subtitle: Text("Quality slim fit jeans"),
),
footer: const GridTileBar(
leading: Text("39.99\$"),
),
child: Container(
color: Colors.lightBlue,
child: const Placeholder(),
),
),
]
),
),
),
);
}