Layouts

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.

Directionality of Row and Column

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:

Overflow warning

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(),
              ),
            ),
          ]
        ),
      ),
    ),
  );
}