Flutter Layout in a nutshell

After struggle with Flutter’s Layout System for a while, it seems i have passed the aha moment, so let’s talk about it.

Key Concepts

there are some key concepts in Flutter’s Layout System. knowing them is essential to help understanding layout.

Unbounded Constraints

either the maximum width or the maximum height is set to double.INFINITY
ScrollView and its descendant

ScrollView and its descendant like ListView or GridView are most seen, other widgets that can set width or height to double.INFINITY also counts. sometimes they are called as big as possible.

Flex

when in bounded constraints, try to be as big as possible in that direction.
when in unbounded constraints, try to fit their children in that direction.
Row and Column

Row and Column are Flex instances, if you want more control, Flex widget is also available. they can be used with Flexible widgets, but not restricted to. (a Text widget in Column is totally fine.)

Flexible

Use it within Flex. Flexible declares what percent space to use.

i.e. flex = 1 means 1/all, if all = 1 + 1, then it’s 50%.

Expanded

Expanded is most seen as a Flexible instance, it will fill the available space in the main axis. (i.e. horizontally for Row or vertically for a Column)


Key Widgets

Container

Containers with no children try to be as big as possible unless the incoming constraints are unbounded, in which case they try to be as small as possible. Containers with children size themselves to their children. The width, height, and constraints arguments to the constructor override this.
return MaterialApp(
home: Scaffold(
body: Container(
color: Colors.yellow,
),
),
);

a container without child will take up all the available space like this:

if width or/and height is set, it will adhere.

return MaterialApp(
home: Scaffold(
body: Container(
color: Colors.yellow,
width: 100,
height: 100,
),
),
);

if it has a Text child, it will respect its size, children always comes first.

return MaterialApp(
home: Scaffold(
body: Container(
color: Colors.yellow,
child: Text('hello'),
),
),
);

besides these, it can align children, make padding / margin, etc. quite useful.

Stack

Stack is like css’s absolute layout. you can overlap several children in a simple way, for example having some text and an image.

Each child of a Stack widget is either positioned or non-positioned. Positioned children are those wrapped in a Positioned widget that has at least one non-null property. The stack sizes itself to contain all the non-positioned children, which are positioned according to alignment.

so if a child is not positioned, Stack will use fit and alignment to put it to a suitable position.

Stack(
fit: StackFit.loose,
alignment: Alignment.center,
children: <Widget>[
Text('world'),
Positioned(
bottom: 10,
child: Text('hello'),
)
],
),

what StackFit.loose saying is if child is not larger than me, use your size. that’s what it looks like:

meanwhile StackFit.expand will put all non positioned children to stack’s size. that’s what it looks like:

Text(‘world’) is now as large as stack, so alignment.center seems no effect.

Row and Column

they are Flex instances, which can put children vertically or horizontally.

crossAxisAlignment means how to align the other side. if it’s Row how to align vertically, if it’s Column how to align horizontally.

mainAxisSize by default its MainAxisSize.max, if you want it to be just the Row’s width or Column’s height, set it to MainAxisSize.min.

SizedBox

use SizedBox to get fixed size box.

SafeArea

use SafeArea will indent the child by enough to avoid the status bar at the top of the screen(get rid of iPhoneX’s Notch).


Principles

Don’t put unbounded constraints widget within Flex

Column is Flex, if you put a ListView in it like this:

you will get layout errors

because Column which is a Flex instance don’t know how to put an as big as possible widget in it. the fix is simple, just set ListView shrinkWrap = true. after doing so, ListView will shrink to min size. now Column know how to put it.

Expanded is usable within Column or Row, because it’s a Flexible, born to work with Flex widgets.

Don’t nest unbounded constraints widgets

i.e. a ListView in ListView

the error is like previous. only one component can be as big as possible in same direction. if you really want to combine 2 or more scrollviews, NestedScrollView may be helpful.

Don’t set flex other than 0 in unbounded widgets

because the space is unlimited, you can’t split it into like 1:2. if you do, this error is waiting for you:

RenderFlex children have non-zero flex but incoming height constraints are unbounded.

Quiz

will these code make Hello World wrapped in 100x100 square?

return MaterialApp(
home: Container(
alignment: Alignment.center,
constraints: BoxConstraints.tight(Size(100, 100)),
decoration: BoxDecoration(color: Colors.yellow),
child: Text('Hello World'),
),
);

The answer is No. it will behave like this:

MaterialApp will make its first child stretch fo fit screen. so even constraints is given, after doing some calculation, system think your constraints should be ignored.

the right way to achieve the expected effect is to wrap a Text widget in a Center or Align widget.

What if I want to get parent’s constraints?

use LayoutBuilder widget to get parent’s constraints.

In certain situation it’s convenient to use’s parent’s constraints to help building child’s UI. Parent doesn’t have a size until the child has a size, all it has is constraints, i.e. maxWidth.

// borrowed from https://stackoverflow.com/a/41558369/94962
var container = Container(
// Toggling width from 100 to 300 will change what is rendered
// in the child container
width: 100.0,
// width: 300.0
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if(constraints.maxWidth > 200.0) {
return Text('BIG');
} else {
return Text('SMALL');
}
}
),
);

How do I get screen’s size?

use MediaQuery, besides size, it has other device info like devicePixelRatio.


So that’s what I think are important to understand flutter’s layout, hope it helps. if there are any mistakes welcome point out. :)