Layouts
- Understanding layout in Flutter
- Lay out a single widget
- Layout multiple widgets vertically or horizontally
- DevTools and debugging layout
- Scrolling widgets
- Adaptive layouts
- Additional resources
- Feedback
Given that Flutter is a UI toolkit, you'll spend a lot of time creating layouts with Flutter widgets. In this section, you'll learn how to build layouts with some of the most common layout widgets. You'll use Flutter DevTools (also called Dart DevTools) to understand how Flutter is creating your layout. Finally, you'll encounter and debug one of Flutter's most common layout errors, the dreaded "unbounded constraints" error.
Understanding layout in Flutter
#The core of Flutter's layout mechanism is widgets. In Flutter, almost everything is a widget — even layout models are widgets. The images, icons, and text that you see in a Flutter app are all widgets. Things you don't see are also widgets, such as the rows, columns, and grids that arrange, constrain, and align the visible widgets.
You create a layout by composing widgets to build more complex widgets. For example, the diagram below shows 3 icons with a label under each one, and the corresponding widget tree:
In this example, there's a row of 3 columns where each column contains an icon and a label. All layouts, no matter how complex, are created by composing these layout widgets.
Constraints
#Understanding constraints in Flutter is an important part of understanding how layout works in Flutter.
Layout, in a general sense, refers to the size of the widgets and their positions on the screen. The size and position of any given widget is constrained by its parent; it can't have any size it wants, and it doesn't decide its own place on the screen. Instead, size and position are determined by a conversation between a widget and its parent.
In the simplest example, the layout conversation looks like this:
- A widget receives its constraints from its parent.
- A constraint is just a set of 4 doubles: a minimum and maximum width, and a minimum and maximum height.
- The widget determines what size it should be within those constraints, and passes its width and height back to the parent.
- The parent looks at the size it wants to be and how it should be aligned, and sets the widget's position accordingly. Alignment can be set explicitly, using a variety of widgets like
Center
, and the alignment properties onRow
andColumn
.
In Flutter, this layout conversation is often expressed with the simplified phrase, "Constraints go down. Sizes go up. Parent sets the position."
Box types
#In Flutter, widgets are rendered by their underlying RenderBox
objects. These objects determine how to handle the constraints they're passed.
Generally, there are three kinds of boxes:
- Those that try to be as big as possible. For example, the boxes used by
Center
andListView
. - Those that try to be the same size as their children. For example, the boxes used by
Transform
andOpacity
- Those that try to be a particular size. For example, the boxes used by
Image
andText
.
Some widgets, for example Container
, vary from type to type based on their constructor arguments. The Container
constructor defaults to trying to be as big as possible, but if you give it a width, for instance, it tries to honor that and be that particular size.
Others, for example Row
and Column
(flex boxes) vary based on the constraints they are given. Read more about flex boxes and constraints in the Understanding Constraints article.
Lay out a single widget
#To lay out a single widget in Flutter, wrap a visible widget, such as Text
or Image
with a widget that can change its position on a screen, such as a Center
widget.
Widget build(BuildContext context) {
return Center(
child: BorderedImage(),
);
}
The following figure shows a widget that isn't aligned on the left, and a widget that has been centered on the right.
All layout widgets have either of the following:
- A
child
property if they take a single child—for example,Center
,Container
, orPadding
. - A
children
property if they take a list of widgets—for example,Row
,Column
,ListView
, orStack
.
Container
#Container
is a convenience widget that's made up of several widgets responsible for layout, painting, positioning, and sizing. In regard to layout, it can be used to add padding and margins to a widget. There is also a Padding
widget that could be used here to the same effect. The following example uses a Container
.
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16.0),
child: BorderedImage(),
);
}
The following figure shows a widget without padding on the left, and a widget with padding on the right.
To create more complex layouts in Flutter, you can compose many widgets. For example, you can combine Container
and Center
:
Widget build(BuildContext context) {
return Center(
Container(
padding: EdgeInsets.all(16.0),
child: BorderedImage(),
),
);
}
Layout multiple widgets vertically or horizontally
#One of the most common layout patterns is to arrange widgets vertically or horizontally. You can use a Row
widget to arrange widgets horizontally, and a Column
widget to arrange widgets vertically. The first figure on this page used both.
This is the most basic example of using a Row
widget.
Each child of Row
or Column
can be rows and columns themselves, combining to make a complex layout. For example, you could add labels to each of the images in the example above using columns.
Align widgets within rows and columns
#In the following example, the widgets are each 200 pixels wide, and the viewport is 700 pixels wide. The widgets are consequently aligned to the left, one after the other, with all the extra space on the right.
You control how a row or column aligns its children using the mainAxisAlignment
and crossAxisAlignment
properties. For a row, the main axis runs horizontally and the cross axis runs vertically. For a column, the main axis runs vertically and the cross axis runs horizontally.
Setting the main axis alignment to spaceEvenly
divides the free horizontal space evenly between, before, and after each image.
Columns work the same way as rows. The following example shows a column of 3 images, each is 100 pixels high. The height of the render box (in this case, the entire screen) is more than 300 pixels, so setting the main axis alignment to spaceEvenly
divides the free vertical space evenly between, above, and below each image.
The MainAxisAlignment
and CrossAxisAlignment
enums offer a variety of constants for controlling alignment.
Flutter includes other widgets that can be used for alignment, notably the Align
widget.
Sizing widgets within rows and columns
#When a layout is too large to fit a device, a yellow and black striped pattern appears along the affected edge. In this example, the viewport is 400 pixels wide, and each child is 150 pixels wide.
Widgets can be sized to fit within a row or column by using the Expanded
widget. To fix the previous example where the row of images is too wide for its render box, wrap each image with an Expanded
widget.
The Expanded
widget can also dictate how much space a widget should take up relative to its siblings. For example, perhaps you want a widget to occupy twice as much space as its siblings. For this, use the Expanded
widgets flex
property, an integer that determines the flex factor for a widget. The default flex factor is 1. The following code sets the flex factor of the middle image to 2:
DevTools and debugging layout
#In certain situations, a box's constraint is unbounded, or infinite. This means that either the maximum width or the maximum height is set to double.infinity
. A box that tries to be as big as possible won't function usefully when given an unbounded constraint and, in debug mode, throws an exception.
The most common case where a render box ends up with an unbounded constraint is within a flex box (Row
or Column
), and within a scrollable region (such as ListView
and other ScrollView
subclasses). ListView
, for example, tries to expand to fit the space available in its cross-direction (perhaps it's a vertically-scrolling block and tries to be as wide as its parent). If you nest a vertically scrolling ListView
inside a horizontally scrolling ListView
, the inner list tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction.
Perhaps the most common error you'll run into while building a Flutter application is due to incorrectly using layout widgets, and is referred to as the "unbounded constraints" error.
If there was only one type error you should be prepared to confront when you first start building Flutter apps, it would be this one.
Decoding Flutter: Unbounded height and width
Scrolling widgets
#Flutter has many built-in widgets that automatically scroll and also offers a variety of widgets that you can customize to create specific scrolling behavior. On this page, you'll see how to use the most common widget for making any page scrollable, as well as a widget for creating scrollable lists.
ListView
#ListView
is a column-like widget that automatically provides scrolling when its content is longer than its render box. The most basic way to use a ListView
is very similar to using a Column
or Row
. Unlike a column or row, a ListView
requires its children to take up all the available space on the cross axis, as shown in the example below.
ListView
s are commonly used when you have an unknown or very large (or infinite) number of list items. When this is the case, it's best to use the ListView.builder
constructor. The builder constructor only builds the children that are currently visible on screen.
In the following example, the ListView
is displaying a list of to-do items. The todo items are being fetched from a repository, and therefore the number of todos is unknown.
Adaptive layouts
#Because Flutter is used to create mobile, tablet, desktop, and web apps, it's likely you'll need to adjust your application to behave differently depending on things like screen size or input device. This is referred to as making an app adaptive and responsive.
One of the most useful widgets in making adaptive layouts is the LayoutBuilder
widget. LayoutBuilder
is one of many widgets that uses the "builder" pattern in Flutter.
The builder pattern
#In Flutter, you'll find several widgets that use the word "builder" in their names or in their constructors. The following list is not exhaustive:
These different "builders" are useful for solving different problems. For example, the ListView.builder
constructor is primarily used to lazily render items in a list, while the Builder
widget is useful for gaining access to the BuildContext
in deeply widget code.
Despite their different use cases, these builders are unified by how they work. Builder widgets and builder constructors all have arguments called 'builder' (or something similar, like itemBuilder
in the case of ListView.builder
), and the builder argument always accepts a callback. This callback is a builder function. Builder functions are callbacks that pass data to the parent widget, and the parent widget uses those arguments to build and return the child widget. Builder functions always pass in at least one argument–the build context– and generally at least one other argument.
For example, the LayoutBuilder
widget is used to create responsive layouts based on the size of the viewport. The builder callback body is passed the BoxConstraints
that it receives from its parent, along with the widgets 'BuildContext'. With these constraints, you can return a different widget based on the available space.
LayoutBuilder (Flutter Widget of the Week)
In the following example, the widget returned by the LayoutBuilder
changes based on whether the viewport is less than or equal 600 pixels, or greater than 600 pixels.
Meanwhile, the itemBuilder
callback on the ListView.builder
constructor is passed the build context and an int
. This callback is called once for every item in the list, and the int argument represents the index of the list item. The first time the itemBuilder callback is called when Flutter is building the UI, the int passed to the function is 0, the second time it's 1, and so on.
This allows you to provide specific configuration based on the index. Recall the example above using theListView.builder
constructor:
final List<ToDo> items = Repository.fetchTodos();
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, idx) {
var item = items[idx];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(item.description),
Text(item.isComplete),
],
),
);
},
);
}
This example code uses the index that's passed into the builder to grab the correct todo from the list of items, and then displays that todo's data in the widget that is returned from the builder.
To exemplify this, the following example changes the background color of every other list item.
Additional resources
#- Common layout widgets and concepts
- Video: OverlayPortal—Flutter Widget of the Week
- Video: Stack—Flutter Widget of the Week
- Tutorial: Layouts in Flutter
- Documentation: Stack documentation
- Sizing and positioning widgets
- Scrollable widgets
- Example code: Work with long lists
- Example code: Create a horizontal list
- Example code: Create a grid list
- Video: ListView—Flutter Widget of the Week
- Adaptive Apps
- Tutorial: Adaptive Apps codelab
- Video: MediaQuery—Flutter Widget of the Week
- Video: Building platform adaptive apps
- Video: Builder—Flutter Widget of the Week
API reference
#The following resources explain individual APIs.
Feedback
#As this section of the website is evolving, we welcome your feedback!
Unless stated otherwise, the documentation on this site reflects the latest stable version of Flutter. Page last updated on 2024-10-11. View source or report an issue.