A Flutter app’s user interface is made up primarily of what are called Widgets. Nothing but Widgets. Some you can see; some you can’t. This article is going to address two you can’t see, but are very important to the user interface. The Row widget and the Column widget.
You can’t readily see them rendered on the screen without Flutter highlighting their borders while debugging for example. However, in most cases, you can readily see their ‘children.’ Their children are, in fact, a List of other Widgets.
Row and Column widgets are used to align their children horizontally or vertically respectively, and to dictate how much space their children should occupy. If you only have one child widget to display, it’s suggested to use an Align widget or a Center widget instead to position and display it. The Row and Column widget are very much alike as you see below.
You can see both classes utilize the same list of named parameters and default values. Further, both use the same-named parameter to accept their List of Widgets. It’s called, children.
Both classes extend the Flex class. The Flex class, by design, displays its List of child Widgets either horizontally or vertically. In fact, you could replace either class with this parent class. However, if you know beforehand how you’re going to display the children either horizontally (Row) or vertically (Column), you might as well use the appropriate class. Right?
How This‘ll Go Down
In this article, we’ll go back and forth between each widget, reviewing each and every named parameter that resides in both. We’ll try not to be too redundant in the examples used since both classes are indeed very much alike. It’s only in the orientation of their widgets on the screen that they really differ. As with my past articles, I will turn to steadfast examples offered by Google’s own documentation. I will even reference the Flutter Gallery (Google’s source of working examples) to demonstrate how these two particular widgets are implemented and affect the user interface.
Screenshots! Not Gists!
As always, I prefer using screenshots over gists to show code in my articles. I find them easier to work with, and easier to read. However, you can click/tap on them to see the code as a gist or in Github. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program mostly on our computers; not on our phones. For now.
What’s Their Role?
You control how a Row or Column widget 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. Makes sense.
Will Render a RenderBox
In turn, in the Flex class, the direction and alignment values are passed to the RenderFlex class. A class which, in turn, is extending the RenderBox class and using three mixins (see above). Hence, it’s got a lot going on under the hood. Note, that those parameter values, if not provided, are assigned default values deep in the framework. There’s even assert statements to assure they are not null. Note, the widget’s Row and Column assigns the same default values as the RenderBox class does deep in the framework to the class properties: mainAxisAlignment, crossAxisAlignment and mainAxisSize. But we digress.
Let’s take some time now to examine those three named parameters as they represent the main purpose for the Row and Column widget. After all, these widgets are to ‘align’ their children widgets in relation to each as well as in relation to the very space taken up on the screen by the Row or Column widgets themselves . By default, a Row and a Column widget is to take up as much of the screen as possible along their ‘main’ axis.
In the next series of screenshots below, I’m going to quickly go through the possible values assigned to these three named parameters. I may not present every possible combination, but you’ll still get an appreciation of what you can do to align the elements that would make up your own user interface.
Now, keep this in mind with every example shown below. The value assigned to the parameter, mainAxisAlignment, determines ‘where’ the children widgets should be placed along the main axis. The parameter, crossAxisAlignment, determines ‘how’ the children widgets are placed along the cross axis. While the parameter, mainAxisSize, determines, after allocating space to children widgets, whether to then maximize or minimize the amount of any remaining free space along the main axis. Get it? Got it? Good. Let’s take a look.
Reading the parameters, from the bottom-up; from the mainAxisSize up to the mainAxisAlignment, let’s describe what sort of alignment will result in each screenshot listed below.
As little space as possible is taken up along the main axis after space is allocated to the child widgets. The ‘largest’ child widget dictates the space taken up along the cross axis. Since they’re all the same size, the cross axis is straightforward — meeting the size of the child widgets. The child widgets will be placed at the start of the space taken up along the main axis.
As much space as possible is taken up along the main axis. The cross axis is straightforward; the child widgets are all the same size. The child widgets will be placed at the start of the space taken up along the main axis.
As much space as possible is taken up along the main axis. The cross axis is straightforward; the child widgets are all the same size. The child widgets are to be placed at the end of the space taken up along the main axis.
As much space as possible is taken up along the main axis. The cross axis is straightforward; the child widgets are all the same size. The child widgets are to be placed in the center of the space taken up along the main axis.
As much space as possible is taken up along the main axis. The cross axis is straightforward; the child widgets are all the same size. An even about of space is to be taken up ‘between’ all the child widgets. There’s no space allocated to either end of the space taken up along the main axis.
As much space as possible is taken up along the main axis. The cross axis is straightforward; the child widgets are all the same size. There is to be space around every child widget along the main axis. The amount of space between the child widgets is 2 x the amount of space at the ends of all the space taken up along the main axis.
As much space as possible is taken up along the main axis. The cross axis is straightforward; the child widgets are all the same size. There is to be the same amount of space around every child widget — even at the ends of all the space taken up along the main axis.
Let’s Get This Across
Changes to the Row’s cross-axis alignment can only be revealed if the child widgets vary in size along that cross axis. It’s the ‘largest’ child widget that dictates the total amount of space taken up along the cross axis. In this particular case, the cross axis is the vertical height of the row. And so, to demonstrate changes to the cross-axis alignment in this Row widget, the second child has been doubled in size. Again, the space allocated to the cross axis must allow for the ‘largest’ child widget.
Let’s Keep It Even
As it is, to keep this article under 20 minutes, I’m going to leave the mainAxisAlignment parameter at the last setting demonstrated, MainAxisAlignment.spaceEvenly. Now, following the same approach, we’ll run through the possible values for the parameter, crossAxisAlignment.
As much space as possible is taken up along the main axis. The child widgets are placed at the top (the start) of the cross axis. The same amount of space is around every child widget.
As much space as possible is taken up along the main axis. The child widgets are placed at the bottom (the end) of the cross axis. The same amount of space is around every child widget.
As much space as possible is taken up along the main axis. The child widgets are placed in the middle (the center) of the cross axis. The same amount of space is around every child widget.
As much space as possible is taken up along the main axis. As much space as possible is taken up by the cross axis. The child widgets are placed in the middle (the center) of the cross axis. The same amount of space is around every child widget.
As little space as possible is taken up along the main axis and yet still allows for all the child widgets. As much space as possible is taken up by the cross axis. The child widgets are placed in the middle (the center) of the cross axis. The same amount of space is around every child widget.
As you see above, simply supplying the ‘baseline’ will result in an error. Another named parameter, textBaseLine, is required and cannot be null. Let’s add that parameter now with the value, TextBaseline.ideographic. It’s used to align symbols. Let’s see what happens.
As little space as possible is taken up along the main axis. As little space as possible is taken up along the cross axis. The child widgets are placed along the end of the cross axis. The same amount of space is around every child widget.
Not much difference to the value, CrossAxisAlignment.end, isn’t it? Being ideographic symbols, the stars are now aligned along their baseline (along the bottom of these symbols). Although, admittedly, it’s not that readily apparent with them being of different sizes. Replace the symbols with alphabetic characters, and it’s even worse. They’re not aligned by their bottoms.
Simply supply the value, TextBaseline.alphabetic. Flutter will recognize the Row widget was passed a List of alphabetic characters. You’ll see they’re now aligned along their alphabetic baseline. Nice.
Back to the value, TextBaseline.ideographic, and with the symbols back to all the same size, you now see they’re aligned by their baseline — along the bottom of each symbol.
My Source Of Inspiration
I must tell you, the black stars and yellow background used in this article was inspired by Tomek Polański’s own article, Flutter Layout Cheat Sheet. It begins with pictures of those black stars on yellow describing the ‘alignment’ properties of the Row and Column widget, but it then continues with a number of other Layout Widgets found in Flutter. It’s a great reference. Highly recommended.
Now For The Other One…
As for the Column widget, we won’t bother going through the alignments again with this widget. It’s much the same after all…just vertically. Of course, you can yourself if you like with the offered gist, Row_Column_Samples.dart.
So like the Row widget, the Column widget is used to align its content. Go to the Flutter Gallery, and you’ll find its working examples inundated with Row and Column widgets. Comment out the ‘alignment’ specifications from some of the Column widgets in some of those examples, and you can now readily guess what will happen.
Tap or click on the screenshot above to get to the source code. From there, you can determine what particular working example it represents. You can then run them yourself with your own copy of the Flutter Gallery. Peachy.
As a further example, below are four screenshots depicting what happens when the parameter, crossAxisAlignment, in the particular working example above is changed to all four of its possible values:
They Don’t Scroll
Now both of these layout widgets don’t scroll their child widgets. If, while painted themselves on the screen, they then run out of room whether horizontally or vertically, you’re going to encounter an error. If you want things to scroll when space is limited, it may be best to instead use the ListView widget.
The Row class documentation demonstrates such an error by the way with its sample code. I’ve recreated it below. In this example, the row of widgets is just too long for the width of the screen. As a result, you’ll get Flutter’s proverbial ‘yellow and black warning stripes.’
Each On Their Own
As it is, at this point, each individual child widget dictates individually how much space they’re going to take up. The Row widget by itself only has so much room, and so, if one of its children takes up that room and more, there’s going to be a visual indication of that in the form of black and yellow stripes.
The solution offered in this example involves the wrapping of the Row’s second child in an Expanded widget. This tells the Row widget that that particular child should be given as much room as possible — squeezing it in so to then get all three child widgets to fit the screen.
The Text Direction Taken
The next named parameter we’ll look at is the parameter, textDirection. Usually, the Flutter app itself has the means to determine the ambient directionality of text by looking at the locale and internationalization settings of the phone itself. Of course, in some parts of the World, text is read from left-to-right while in other parts of the World, text is read from right-to-left. You can explicitly dictate the direction with this named parameter.
Looking at the screenshots above, note the first simulation doesn’t have the parameter, textDirection, specified. There’s a line commented out. Flutter has determined, in this case, the text direction should be from ‘left to right’ and from ‘top to bottom.’ And so you see the two Text object’s display their content as expected. Simply, my laptop is attuned to the English language. The first object’s text is displayed first then the second object is displayed second. Makes sense. The text is starting from the left side of the screen implying it should be read from left to right.
Note, the second simulation results when that line now uncommented. The parameter, textDirection, is assigned the value, TextDirection.rtl. The property, rtl, means right-to-left of course. Now the content is displayed just the opposite. The text direction is explicitly set from ‘right to left’ and from ‘bottom to top.’ As so, as you readily see, the text is starting from the right side of the screen implying it’s to be read from right to left. Easy enough to understand, right? Now it’s still English sentences, and so it’s a little disorienting. You’ll find yourself still reading each individual sentence from left-to-right, but in Arabic for example, you’d read each sentence right-to-left.
By the way, we can see where this parameter is implemented in the framework. In the Row widget’s parent class, Flex, we see if the parameter, textDirection, is not provided ( ?? operator) and the text direction is needed, Flutter turns to the current ‘context’ (i.e. the app’s settings and or the phone’s setting) to determine it. Otherwise, the parameter is the returning value coming from the function, getEffectiveTextDirection().
Same Thing Going Vertical
Let’s simply change that Row widget to a Column widget and try the same exercise. We’ll examine the last parameter found in both the Row and Column widget. It’s called, verticalDirection. Above, the first simulation doesn’t have the parameter specified. It’s commented out. Flutter has determined, in this case, the text direction should be from ‘left to right’ and from ‘top to bottom.’ And so you see the two Text object’s display their content as expected. First line first; second line second starting from the top. We’ll now include that parameter and set it to VeriticalDirection.up. Everything will now start from the bottom and not from the top of the screen. You’re expected, I would think, to now read from the bottom-up, and so, in that way, it’s still the first line first and then the second line second. Got it? Good.
Nested Rows and Columns
You’re going to use Row and Column widgets a lot in your user interface. I mean a lot. Nesting Rows and Columns are not uncommon in Flutter. Get use to the idea now, and you’ll be well on your way in making effective user interfaces in your Flutter apps.
You’ll see in Flutter’s own documentation, Layouts in Flutter, even simple interfaces have more going on than you readily see. Above, on the first line, we see six widgets. Three Icon widgets; three Text widgets. However, as you see on the second line, there are four more unseen widgets. One Row widget and three Column widgets. Each Column widget contains visible widgets.
Debunk By Debug
Importing the Flutter library, rendering.dart, into the Shrine app (one of the Studies again found in the Flutter Gallery) will allow you to readily see those unseen Row and Column widgets and debunk any thoughts about possibly there being a limited role for these widgets in your apps. For example, set the high-level debugging flag, debugPaintLayerBordersEnabled, to true and you’ll see the many Rows and Columns that exist in even the simplest of interfaces.
Further on in the Flutter documentation under Layouts in Flutter, you’ll see other instances where the elements of the interface are aligned and positioned by the underlying, unseen, but important Row and Column widgets. Yes, you’ll find yourself using them often, but now you know how.