As Flutter quickly becomes one of the leading technologies in app development, it is important for even someone in the web dev environment to check out. Coming from a background of HTML, CSS and JavaScript, the way Flutter handled everything seemed extremely foreign to me. So we’re going to be try and simplify it to just the essentials of structuring the elements in our app to create a simple layout using the Material Components.
Already having Flutter installed and an emulator set up is necessary, we covered getting started in Flutter here. Becoming familiar with the official docs is also always a plus.
The Scaffold
instantiates our main structure, usually whatever is consistent across our app like our appbar or navigation, then we’ll set our body
to the more interesting part of our app, abstracting it out of runApp
will also allow us to use hot reload.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('I\'m an App'),
backgroundColor: Colors.red[600],
),
body: App(),
),
),
);
}
class App extends StatelessWidget {
Widget build(BuildContext context) {
return Container();
}
}
And here’s what this looks like:
Like with HTML div
’s, we can wrap our containers to give us more control over our elements when we can’t manipulate them themselves, since not every widget has properties like width or padding. Containers have some of the same properties from CSS like height
, width
, padding
, and margin
. By default, they will take up the maximum space of their children, empty containers try to take up the maximum amount of space of their parent.
Controlling the spacing can be a bit weird, instead of directly setting padding or margin to a number of pixels, we need to set it to a property on EdgeInsets
and pass in our value in pixels. There is also the Padding
widget that you can wrap your elements in, but you still need to pass-in padding the same as before so it’s really just to be explicit.
EdgeInsets.all()
EdgeInsets.only(left: 0, top: 0, right: 0, bottom: 0)
EdgeInsets.symmetric(vertical: 0, horizontal: 0)
EdgeInsets.fromLTRB(left, top, right, bottom)
Just takes the values without them be explicitly assigned.class App extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
margin: EdgeInsets.only(top: 20, right: 50),
child: Container(
color: Colors.green,
// Add 200px on top and bottom
margin: EdgeInsets.symmetric(vertical: 200),
child: Container(
color: Colors.yellow,
margin: EdgeInsets.fromLTRB(0, 20, 200, 20),
),
),
);
}
}
And now we have a bunch of containers:
The problem with containers is that we can only have one child in each. The column and row widgets let us use a more flexible version of containers while controlling the direction of our elements. We can’t just pass child
to them for each element, we need to set children
to an array of the type <Widget>
, then pass in each of our elements.
If we put a container inside of an Expanded
widget, it will then take up the maximum amount of space of its column or row.
Similar to how flexbox works on the web, we can use mainAxisAlignment
and crossAxisAlignment
to do things the same as justify-content
and align-items
. We even have the same options of start
, center
, end
, spaceEvenly
, spaceAround
, spaceBetween
, and stretch
.
class App extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
// Row 1
Row(
children: <Widget>[
Container(
color: Colors.blue, height: 40, width: 40, child: Text('1')),
Container(
color: Colors.blue, height: 40, width: 40, child: Text('2')),
Container(
color: Colors.blue, height: 40, width: 40, child: Text('3')),
],
),
// Row 2
Row(
children: <Widget>[
Container(
color: Colors.blue, height: 40, width: 40, child: Text('1')),
//Will expand to fill all remaining space
Expanded(
child: Container(
color: Colors.green,
height: 40,
width: 40,
child: Text('2'))),
Container(
color: Colors.blue, height: 40, width: 40, child: Text('3')),
],
),
//Row 3
Container(
height: 100,
child: Row(
//Stretches to vertically fill its parent container
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: Colors.blue,
height: 40,
width: 40,
child: Text('1')),
Expanded(
child: Container(
color: Colors.green,
height: 40,
width: 40,
child: Text('2'))),
Container(
color: Colors.blue,
height: 40,
width: 40,
child: Text('3')),
],
)),
// Row 4
Row(
//Creates even space between each item and their parent container
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Container(
color: Colors.blue, height: 40, width: 40, child: Text('1')),
Container(
color: Colors.blue, height: 40, width: 40, child: Text('1')),
Container(
color: Colors.blue, height: 40, width: 40, child: Text('3')),
],
)
]);
}
}
And here’s a screenshot of our rows and columns:
While working with layouts in Flutter may seem a bit more clunky and verbose that on the web at first glance, it’s good to keep in mind that we also don’t have to deal with the complexity of dynamic grids or bloated media queries for every screen size. At times, layouts in Flutter may seem less powerful than the CSS you’re used to, because it really doesn’t have to be. With just a few containers, columns, rows, and spacing you can get most of the structures than you’ll ever need.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!