Learning a new UI engine is always tricky. Thankfully, the Flutter team has highlighted the basic widgets. For this code tutorial, we will focus on Row and Column, which lay out a list of child widgets in the horizontal and vertical direction respectively.

First off, my favourite thing about them is that, even though you may mistake them for Android SDK’s LinearLayout at first, they are actually far more flexible, yet easy to understand.

To figure out how Row and Column work, we will start from 3 different mock-ups and lay them out using them. We will do this as part of an app with 3 screens (one screen per example).

For each example, we will have a rough sketch of what we want, with notes to describe how each part of the layout behaves. Why rough hand drawn sketches and not fancy mock ups done in Photoshop? Because I want to show you a simple process that you can apply to your own mock ups. Oh, and also because pen and paper is a very useful tool (see Tip 3 of 4 tips to boost your software development career).

A few basic rules to remember

The documentation is rich in explanations about how Flutter lays things out, but, when you’re starting out, this is a bit daunting. So, I’ve extracted a few basic Flutter UI rules that will allow you to build many layouts.



if you want to split your screen horizontally, ie several elements to the right or left of each other, use a Row

if you want to split your screen vertically, ie several elements above or below each other, use a Column

if you want to specify padding, a background colour, or a fixed height or width for an item, wrap it inside a Container

if you want a child element of a Row or Column to expand to fill in the available space, wrap it inside an Expanded

elements have sensible names and the search feature on the website is pretty good, so use it! For example, if you search for text, you will see both the Text widget and a page listing the different types of available Text widgets.

Looking for a Flutter job? Check out my job board dedicated to Flutter at flutterjobs.info

Code tutorial setup

To follow the code tutorial, create a new app as follows.

Create new app flutter create rowcolumnexample 1 flutter create rowcolumnexample

If you’re unsure how to set up a Flutter app, check out Getting started with Flutter official tutorial.

We will set up the app to have 3 screens, namely Example1Page, Example2Page, and Example3Page.

So let’s amend main.dart to launch the app on the first example screen.

main.dart import 'package:flutter/material.dart'; import 'example1_page.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'Row & Column Example', theme: new ThemeData( primaryColor: const Color(0xFF43a047), accentColor: const Color(0xFFffcc00), primaryColorBrightness: Brightness.dark, ), home: new Example1Page(), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import 'package:flutter/material.dart' ; import 'example1_page.dart' ; void main ( ) { runApp ( new MyApp ( ) ) ; } class MyApp extends StatelessWidget { // This widget is the root of your application. @ override Widget build ( BuildContext context ) { return new MaterialApp ( title : 'Row & Column Example' , theme : new ThemeData ( primaryColor : const Color ( 0xFF43a047 ) , accentColor : const Color ( 0xFFffcc00 ) , primaryColorBrightness : Brightness . dark , ) , home : new Example1Page ( ) , ) ; } }

Now, we create example1_page.dart, example2_page.dart and example3_page.dart, with a simple navigation from 1 to 2 and from 1 to 3 via buttons in the toolbar.

example1_page.dart import 'package:flutter/material.dart'; import 'example2_page.dart'; import 'example3_page.dart'; // Note: for simplicity, this is a stateless widget but, in a real app, // a full screen is likely to be a stateful widget. class Example1Page extends StatelessWidget { @override Widget build(BuildContext context) { List<Widget> menu = <Widget>[ new IconButton( icon: new Icon(Icons.send), tooltip: 'To Example 2', onPressed: () => _toExample2(context), ), new IconButton( icon: new Icon(Icons.help), tooltip: 'To Example 3', onPressed: () => _toExample3(context), ) ]; return new Scaffold( appBar: new AppBar( title: new Text("Example 1 Page"), actions: menu, ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 0.0), child: new Text('Example Page 1 content'), ), ); } void _toExample2(BuildContext context) { Navigator.of(context).push(new MaterialPageRoute<dynamic>( builder: (BuildContext context) { return new Example2Page(); }, )); } void _toExample3(BuildContext context) { Navigator.of(context).push(new MaterialPageRoute<dynamic>( builder: (BuildContext context) { return new Example3Page(); }, )); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import 'package:flutter/material.dart' ; import 'example2_page.dart' ; import 'example3_page.dart' ; // Note: for simplicity, this is a stateless widget but, in a real app, // a full screen is likely to be a stateful widget. class Example1Page extends StatelessWidget { @ override Widget build ( BuildContext context ) { List < Widget > menu = < Widget > [ new IconButton ( icon : new Icon ( Icons . send ) , tooltip : 'To Example 2' , onPressed : ( ) = > _toExample2 ( context ) , ) , new IconButton ( icon : new Icon ( Icons . help ) , tooltip : 'To Example 3' , onPressed : ( ) = > _toExample3 ( context ) , ) ] ; return new Scaffold ( appBar : new AppBar ( title : new Text ( "Example 1 Page" ) , actions : menu , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 0.0 ) , child : new Text ( 'Example Page 1 content' ) , ) , ) ; } void _toExample2 ( BuildContext context ) { Navigator . of ( context ) . push ( new MaterialPageRoute < dynamic > ( builder : ( BuildContext context ) { return new Example2Page ( ) ; } , ) ) ; } void _toExample3 ( BuildContext context ) { Navigator . of ( context ) . push ( new MaterialPageRoute < dynamic > ( builder : ( BuildContext context ) { return new Example3Page ( ) ; } , ) ) ; } }

example2_page.dart import 'package:flutter/material.dart'; // Note: for simplicity, this is a stateless widget but, in a real app, // a full screen is likely to be a stateful widget. class Example2Page extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Example 2 Page"), ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 0.0), child: new Text('Example Page 2 content'), ), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import 'package:flutter/material.dart' ; // Note: for simplicity, this is a stateless widget but, in a real app, // a full screen is likely to be a stateful widget. class Example2Page extends StatelessWidget { @ override Widget build ( BuildContext context ) { return new Scaffold ( appBar : new AppBar ( title : new Text ( "Example 2 Page" ) , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 0.0 ) , child : new Text ( 'Example Page 2 content' ) , ) , ) ; } }

example3_page.dart import 'package:flutter/material.dart'; // Note: for simplicity, this is a stateless widget but, in a real app, // a full screen is likely to be a stateful widget. class Example3Page extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Example 3 Page"), ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 0.0), child: new Text('Example Page 3 content'), ), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import 'package:flutter/material.dart' ; // Note: for simplicity, this is a stateless widget but, in a real app, // a full screen is likely to be a stateful widget. class Example3Page extends StatelessWidget { @ override Widget build ( BuildContext context ) { return new Scaffold ( appBar : new AppBar ( title : new Text ( "Example 3 Page" ) , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 0.0 ) , child : new Text ( 'Example Page 3 content' ) , ) , ) ; } }

Example 1

For this example, we will create a screen with a subtitle (below the toolbar) which is always shown, a call to action button at the bottom, which is also always shown, and some other content in between the two.

From mock to Flutter widgets

Let’s start with a sketch of the screen, annotated with descriptions in plain English. Then, we convert the descriptions into Flutter widgets.

Code

Now, we implement the design in example1_page.dart.

example1_page.view/build @override Widget build(BuildContext context) { List<Widget> menu = <Widget>[ new IconButton( icon: new Icon(Icons.send), tooltip: 'To Example 2', onPressed: () => _toExample2(context), ), new IconButton( icon: new Icon(Icons.help), tooltip: 'To Example 3', onPressed: () => _toExample3(context), ) ]; Widget subtitle = new Container ( padding: new EdgeInsets.all(8.0), color: new Color(0X33000000), child: new Text('Subtitle'), ); Widget middleSection = new Expanded( child: new Container ( padding: new EdgeInsets.all(8.0), color: new Color(0X9900CC00), child: new Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ new Text('Some data here'), new Text('More stuff here'), ], ), ), ); Widget bottomBanner = new Container ( padding: new EdgeInsets.all(8.0), color: new Color(0X99CC0000), height: 48.0, child: new Center( child: new Text('Bottom Banner'), ), ); Widget body = new Column( // This makes each child fill the full width of the screen crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: <Widget>[ subtitle, middleSection, bottomBanner, ], ); return new Scaffold( appBar: new AppBar( title: new Text("Example 1 Page"), actions: menu, ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 0.0), child: body, ), ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 @ override Widget build ( BuildContext context ) { List < Widget > menu = < Widget > [ new IconButton ( icon : new Icon ( Icons . send ) , tooltip : 'To Example 2' , onPressed : ( ) = > _toExample2 ( context ) , ) , new IconButton ( icon : new Icon ( Icons . help ) , tooltip : 'To Example 3' , onPressed : ( ) = > _toExample3 ( context ) , ) ] ; Widget subtitle = new Container ( padding : new EdgeInsets . all ( 8.0 ) , color : new Color ( 0X33000000 ) , child : new Text ( 'Subtitle' ) , ) ; Widget middleSection = new Expanded ( child : new Container ( padding : new EdgeInsets . all ( 8.0 ) , color : new Color ( 0X9900CC00 ) , child : new Column ( crossAxisAlignment : CrossAxisAlignment . stretch , children : < Widget > [ new Text ( 'Some data here' ) , new Text ( 'More stuff here' ) , ] , ) , ) , ) ; Widget bottomBanner = new Container ( padding : new EdgeInsets . all ( 8.0 ) , color : new Color ( 0X99CC0000 ) , height : 48.0 , child : new Center ( child : new Text ( 'Bottom Banner' ) , ) , ) ; Widget body = new Column ( // This makes each child fill the full width of the screen crossAxisAlignment : CrossAxisAlignment . stretch , mainAxisSize : MainAxisSize . min , children : < Widget > [ subtitle , middleSection , bottomBanner , ] , ) ; return new Scaffold ( appBar : new AppBar ( title : new Text ( "Example 1 Page" ) , actions : menu , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 0.0 ) , child : body , ) , ) ; }

Voila!

Example 2

For this example, we will create a screen with a subtitle (below the toolbar) which is always shown, a list which takes up 2 / 3 of the space below the subtitle, and a grid which takes up 1/ 3 of the space below the subtitle.

From mock to Flutter widgets

Let’s start with a sketch of the screen, annotated with descriptions in plain English. Then, we convert the descriptions into Flutter widgets.

Code

Now, we implement the design in example2_page.dart.

example2_page.view/build @override Widget build(BuildContext context) { Widget subtitle = new Container ( padding: new EdgeInsets.all(8.0), color: new Color(0X33000000), child: new Text('Subtitle'), ); Widget listSection = new Expanded( flex: 2, child: new ListView( scrollDirection: Axis.vertical, shrinkWrap: true, children: _generateListItems().map((String value) { return _displayListItem(value); }).toList()), ); Widget gridSection = new Expanded( flex: 1, child: new GridView.count( crossAxisCount: 4, childAspectRatio: 1.0, mainAxisSpacing: 4.0, crossAxisSpacing: 4.0, children: _generateGridItems().map((String value) { return _displayGridItem(value); }).toList()), ); Widget body = new Column( // This makes each child fill the full width of the screen crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: <Widget>[ subtitle, listSection, gridSection, ], ); return new Scaffold( appBar: new AppBar( title: new Text("Example 2 Page"), ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 0.0), child: body, ), ); } Widget _displayListItem(String value) { return new Container ( padding: new EdgeInsets.all(8.0), color: new Color(0X9900CCCC), child: new Text(value), ); } Widget _displayGridItem(String value) { return new Container ( padding: new EdgeInsets.all(8.0), color: new Color(0X99CCCC00), child: new Text(value), ); } // Note: Placeholder method to generate list data. List<String> _generateListItems() { List<String> listItems = new List<String>(); for (int i = 0; i < 20; i++) { listItems.add('List Item ' + i.toString() + ' title and description'); } return listItems; } // Note: Placeholder method to generate grid data List<String> _generateGridItems() { List<String> gridItems = new List<String>(); for (int i = 0; i < 24; i++) { gridItems.add('GI ' + i.toString()); } return gridItems; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 @ override Widget build ( BuildContext context ) { Widget subtitle = new Container ( padding : new EdgeInsets . all ( 8.0 ) , color : new Color ( 0X33000000 ) , child : new Text ( 'Subtitle' ) , ) ; Widget listSection = new Expanded ( flex : 2 , child : new ListView ( scrollDirection : Axis . vertical , shrinkWrap : true , children : _generateListItems ( ) . map ( ( String value ) { return _displayListItem ( value ) ; } ) . toList ( ) ) , ) ; Widget gridSection = new Expanded ( flex : 1 , child : new GridView . count ( crossAxisCount : 4 , childAspectRatio : 1.0 , mainAxisSpacing : 4.0 , crossAxisSpacing : 4.0 , children : _generateGridItems ( ) . map ( ( String value ) { return _displayGridItem ( value ) ; } ) . toList ( ) ) , ) ; Widget body = new Column ( // This makes each child fill the full width of the screen crossAxisAlignment : CrossAxisAlignment . stretch , mainAxisSize : MainAxisSize . min , children : < Widget > [ subtitle , listSection , gridSection , ] , ) ; return new Scaffold ( appBar : new AppBar ( title : new Text ( "Example 2 Page" ) , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 0.0 ) , child : body , ) , ) ; } Widget _displayListItem ( String value ) { return new Container ( padding : new EdgeInsets . all ( 8.0 ) , color : new Color ( 0X9900CCCC ) , child : new Text ( value ) , ) ; } Widget _displayGridItem ( String value ) { return new Container ( padding : new EdgeInsets . all ( 8.0 ) , color : new Color ( 0X99CCCC00 ) , child : new Text ( value ) , ) ; } // Note: Placeholder method to generate list data. List < String > _generateListItems ( ) { List < String > listItems = new List < String > ( ) ; for ( int i = 0 ; i < 20 ; i ++ ) { listItems . add ( 'List Item ' + i . toString ( ) + ' title and description' ) ; } return listItems ; } // Note: Placeholder method to generate grid data List < String > _generateGridItems ( ) { List < String > gridItems = new List < String > ( ) ; for ( int i = 0 ; i < 24 ; i ++ ) { gridItems . add ( 'GI ' + i . toString ( ) ) ; } return gridItems ; }

Voila!

Example 3

For this example, we will create a widget that would most likely be used in a list. It has an icon on the left and one on the right, a title which itself is made up of an image, and 2 separate pieces of text (one of those to be truncated as required), and a description to be restricted to 2 lines.

From mock to Flutter widgets

Let’s start with a sketch of the screen, annotated with descriptions in plain English. Then, we convert the descriptions into Flutter widgets.

Code

Now, we implement the design in example3_page.dart.

example3_page.view/build @override Widget build(BuildContext context) { Widget titleRow = new Row( children: <Widget>[ new Icon(Icons.people), new Expanded( child: new Container( padding: new EdgeInsets.symmetric(horizontal: 4.0), child: new Text('This is quite a long name that will be cut off', overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ), new Text('0111 222 333'), ], ); Widget textSection = new Container( color: new Color(0X3300CC00), child: new Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: <Widget>[ titleRow, new Text('We have a description here. It may fit on 2 lines, ' 'or it may fit on 1 line. Or will require more lines than 2, but' ' we only show 2 lines maximum f f f f f f f f f f f f f f f .', overflow: TextOverflow.ellipsis, maxLines: 2,) ], ), ); Widget body = new Container( color: new Color(0X33000000), child: new Row( children: <Widget>[ new IconButton( onPressed: null, // Not implemented in this code tutorial icon: new Icon(Icons.send), ), new Expanded( child: textSection), new IconButton( onPressed: null, // Not implemented in this code tutorial icon: new Icon(Icons.edit), ), ], ), ); return new Scaffold( appBar: new AppBar( title: new Text("Example 3 Page"), ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 0.0), child: body, ), ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 @ override Widget build ( BuildContext context ) { Widget titleRow = new Row ( children : < Widget > [ new Icon ( Icons . people ) , new Expanded ( child : new Container ( padding : new EdgeInsets . symmetric ( horizontal : 4.0 ) , child : new Text ( 'This is quite a long name that will be cut off' , overflow : TextOverflow . ellipsis , maxLines : 1 , ) , ) , ) , new Text ( '0111 222 333' ) , ] , ) ; Widget textSection = new Container ( color : new Color ( 0X3300CC00 ) , child : new Column ( crossAxisAlignment : CrossAxisAlignment . stretch , mainAxisSize : MainAxisSize . min , children : < Widget > [ titleRow , new Text ( 'We have a description here. It may fit on 2 lines, ' 'or it may fit on 1 line. Or will require more lines than 2, but' ' we only show 2 lines maximum f f f f f f f f f f f f f f f .' , overflow : TextOverflow . ellipsis , maxLines : 2 , ) ] , ) , ) ; Widget body = new Container ( color : new Color ( 0X33000000 ) , child : new Row ( children : < Widget > [ new IconButton ( onPressed : null , // Not implemented in this code tutorial icon : new Icon ( Icons . send ) , ) , new Expanded ( child : textSection ) , new IconButton ( onPressed : null , // Not implemented in this code tutorial icon : new Icon ( Icons . edit ) , ) , ] , ) , ) ; return new Scaffold ( appBar : new AppBar ( title : new Text ( "Example 3 Page" ) , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 0.0 ) , child : body , ) , ) ; }

Voila!

What next?

From one of your favourite apps, or an app you are working on now, choose a screen, and work out how you would lay it out in Flutter.

Check out Being a software developer in 2020!

Related