Flutter Layout

The Flutter Layout System

Copy from https://blog.codemagic.io/flutter-tutorial-flutter-layout-system/

There are several points to know when working with Flutter’s Layout System.

Constrain Yourself

Box Constraints may not make the world go around, but they sure make Flutter go around. Constraints is basically just a fancy sounding term for Size Limits. There are maximum sizes, minimum sizes and sometimes there are no sizes specified so your app has to figure it out for itself at runtime.

  • Examples of maxes include Center, Listview and if you don’t tell a Container what size to be, it will try to be as big as it can, too.
  • Minimum seekers include Opacity and Transform.
  • Some try to be a particular size, such as Image and Text.

And then there are the fun ones. Yes, that was sarcasm, good catch on your part. Anyone who has ever dealt with the Boogeyman of all Flutter errors, “Renderflex children have non-zero flex but incoming Constraints are unbounded”, can tell you there are times when trying to deal with Constraints can make you want to tear your hair out. More on that later…

Flutter Layout: One Pass to Layout Them All and on the Screen, Render Them

One of the best things about Flutter is that, unlike some other frameworks, Flutter does its layout in only one pass; down and back up. Flutter is able to do this because of the way it lays the elements out in relation to each other, instead of in relation to the physical screen or window size.

The trip down the tree for widgets with only one child each looks something like this:

  • On the way down, the parent of each Widget tells it the maximum amount of space it has to work with in the X and Y axis. This is called the Widget’s “Constraints”.
  • If that Widget is a parent, it will then calculate how much space it needs for things like borders, padding, etc., subtract that from its Constraints and then pass the resulting dimensions on to its child. Those will be its child’s Constraints.
  • If the Widget is not a parent, it first figures out how much space it needs and then passes that information back up the tree.
  • Now that child’s parent knows something more important than how large its child could be. It knows exactly how big the child is going to be.
  • Armed with that knowledge, it can align the child within itself.
  • That completed, it can now figure out how large it’s going to be and pass that information up the tree to its parent.
  • Rinse and repeat…

In reality, it looks something like this with a fullscreen app on a 1920x1080 screen (code below):

  • The highest level Container is going to be 1920x1080. It has padding of 30 and 10 on each axis.
  • The paddings have eaten 40 in each axis, so there is 1880x1040 remaining. This is what’s sent to the child as the child’s Constraints. So 1880x1040 is the largest the child could be.
  • The child has no children of its own. It calculates its size to be 400x200, and reports that back to its parent. “I’m actually going to be 400x200”.
  • Now the parent knows it has 1720x880 worth of empty space to work with and the child only needs 400x200. So, if the child isn’t going to take up all of the available space, then where do we put the child? Top? Left? Right? Center? 22% Left and 9% Down? How do we figure this out?
  • This is where alignment comes in. You’ll see that one of many ways to align something is to use the alignment property of the Container class. If we’re using Alignment.topLeft, then our child will be placed, you guessed it, in the top left corner of the highest level Container.

Side note: Sizes in Flutter are not marked as being dp, sp or px. This is because everything in Flutter is in dp, which makes life a whole lot easier. It also helps when it comes time to have the app adapt to different screen sizes, too!

The Code for the Example Above

class SampleClass extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: double.infinity,
      width: MediaQuery.of(context).size.width,
      alignment: Alignment.topLeft,
      padding: EdgeInsets.only(top: 10, bottom: 20, right: 20, left: 10),
      color: Colors.green,
      child: Container(
        height: 200,
        width: 400,
      ),
    );
  }
}

That’s the basic idea. Things get a lot more complicated when you start dealing with the Multi-Child Layout Algorithm, used for things like Rows and Columns; but since this is only Flutter 102, we’ll keep it simple for now.

In the above, I show two ways to set a size programmatically. MediaQuery does many things, including tell you the height and width of the physical screen. It can be used if you have a MaterialApp Widget higher up in the tree. The MaterialApp is often placed at the root of an app in order to make life easier. In the above example, we’re setting the top-level Container’s width to match the device’s screen size.

The other method is “double.infinty”, which is roughly the same thing as “match_parent” in Android. It basically means, “be as big as you can while still staying within the Constraints you were given”.

”…while still staying within the Constraints you were given”. Oh boy, now we put our foot in a big hole. What if you weren’t given any Constraints? Yes, it can happen…

“Incoming Constraints are Unbounded” and Other Nightmares

Try holding 10,000 liters of water without using anything to contain it. No can, no bottle, no tank… Not even a coffee cup. Go ahead, I’ll wait…

Didn’t work out so well, did it? Well, now you know what trying to hold onto something really big is like if you haven’t set any boundaries to contain it. Congratulations! You now understand what happens if you try to set your Widget size to double.infinity when it hasn’t been passed any Constraints.

One situation in which you might not have any Constraints is when you use a Row or Column. That’s because each child of the Row or Column could potentially be a different size, and the Row or Column has no idea how big those children are going to need to be. There are other things you can do in order to fix this, of course. But in general, a Row or Column is waiting to hear from its children; then it adds their reported sized together to determine its own size.

But what if one of those children wants to be infinitely large? Then you end up standing in a mud puddle with water, water, everywhere.

Going Out of Bounds

Sometimes you try to put ten pounds of stuff into a five pound bag, and that doesn’t work any better than trying to hold all that water in your hands. In this case, I’m referring to times when your Widget is trying to use more real estate than its Constraints offered it. In Flutter we call this an Overflow, and when that happens the framework gives you a great big warning with lots of black and yellow bars on the exact spot where you overflowed the screen. It even tells you how much you overflowed by, so you know how much to adjust for!

Now that you know a few ways you can go wrong, let’s take a look at some ways you can go right.

Flutter resources you do not want to miss

A friend of mine once said, “If you’re writing code on one screen and you don’t have the documentation open in another, you’re doing it wrong.”

This is just as true with Flutter as it is with anything else. However, in Flutter’s case we have a lot more available to us than just the API docs. The Flutter and Dart teams have put together an incredible array of documentation that includes examples, tutorials, a cookbook and even several video series on Youtube. You’re definitely going to want to add these to your reference library

When your first go to the Flutter docs page you might be tempted to skip straight to what you think you’re looking for and ignore the rest. This would be a huge mistake!

The docs section of Flutter.dev is actually a portal to almost everything you could ever want to know about Flutter and I’m not talking even about the API.

  • Guides, guides and more guides for lots of things, from Staggered Animations to handling Interactivity.
  • The latest (it keeps changing) information in State Management in Flutter.
  • Packages, Plugins and even Platform Integration.
  • Using assets, images, fonts, as well as handling gestures and touches.
  • Tutorials, Codelabs, mini-courses and more!

There’s even a guide to Layouts in Flutter … but don’t tell the nice people who asked me to write this article or I might be out of a job!

By the way, I haven’t even mentioned the courses yet.

  • Coding with Flutter, by Andrea Bizzotto
  • The Flutter Crash Course, by Nick Manning
  • The London AppBrewery’s Flutter Bootcamp was designed with input from the Flutter team, and the price is being subsidized by Google so it only costs you ten bucks (USD)!
  • It’s not really a course, but Fluttery’s Flutter Challenge walks you through the process of finding some really cool design on the internet and figuring out how to make it happen in Flutter. Everyone who seriously works in Flutter has studied at least a few of these.

Here are a few other key parts of Flutter docs you should be filling your Flutter reference bookmarks folder with:

One of the most used, and most useful things in all of Flutter is a video series of quick, 1-3 minute shorts that each go into a single widget and show you how to use it. The “Widget of the Week” playlist is something you should have open in a browser tab before you even open your IDE!

You think that’s boring? No, this is boring.

In Flutter we have something I’m not sure exists anywhere else. We get to watch Flutter team members sit in front of a camera and try to do things… and screw up.

The video is unedited and you get to watch as they can’t remember things from their own API (there’s too much to remember!) and even see them get just as stuck as you do.

One (of several) things the show addresses is that people often think everyone else understands this stuff with no problem and, “I’m the only idiot who can’t figure it out”. This is a huge source of imposter syndrome and, thanks to the brave members of the Flutter and Dart teams who aren’t afraid to screw up while the whole world is watching, we get to see that we’re actually not any dumber than they are!

Check it out here!

Closing Out the List (No, not the , the other List)

One of the greatest places to start learning about Flutter is the team’s own curated list of resources. These round up everything from the Boring Show to the Widget of the Week, from the Flutter Challenge series by Fluttery to the London AppBrewery’s great Flutter Bootcamp course and more!

Flutter Tutorial: Flutter Layout System. Part II

Copy from https://blog.codemagic.io/flutter-tutorial-part-2/ Sep 19, 2019

Basic Flutter Widgets, the Tools of the Trade

Please note this is the second part on Flutter tutorial by Scott Stoll. You can find the first part of this Flutter tutorial here.

In this tutorial you will learn about:

  • Introduction to containers
  • Rows and columns
  • What are Flutter layers
  • Flexibles: Flex vs flex
  • Image Auto-Resize Using Expanded
  • How to fix errors with Rows and Columns

Container

Containers are perhaps the most used, and overused, Widgets in all of Flutter. They aren’t the same as a <div> and you shouldn’t use them like one. Containers have a higher overhead than things like a SizedBox, so if all you need is a box to put things in then you’d probably be better off using something else.

That said, you’ll use Containers all over the place and they can do a ton of things for you. Containers can align their contents, act as colored boxes, apply padding and margin. If you use the decoration property of a Container, which is usually used with a BoxDecoration, this opens up possibilities that can keep you busy playing around for days with things like shapes, colors, gradients and borders.

You might have noticed that color was mentioned in both the Container and the BoxDecoration. If you try to use both, the analyzer will roll around on the ground in pain and tell you that you can’t do that. This is because the color property of the Container is actually just a shorthand version of the color properly of the BoxDecoration, which allows you to have an easy way to set a solid color for your Container if that’s all you need. If you use the color properties of both the Container and the BoxDecoration, then for all intents and purposes you’re trying to call the same parameter twice.

That’s a no-no.

Rows and Columns

Two of the most important Widgets to master, and the bane of every Flutter developer’s existence, are Rows and Columns. Whether you want to create a complex news app or a some simple buttons with an icon and text aligned to each other, you have to nest Rows and Columns… And when you nest them, all kinds of things can start getting very weird, very fast.

Rows and Columns both extend the Flex class, meaning they’re Subclasses and Flex is their Superclass. The only real difference between a Row and Column is that the Row is a Flex that has it’s direction property hardcoded to horizontal, and the Column’s is hardcoded to vertical.

One thing that tends to confuse a lot of people new to Flutter is when your Row or Column blows up in your face, the error never says Row or Column. Newcomers are stuck staring at an error message, scratching their heads and trying to understand what a RenderFlex is.

Flutter Layers: Let’s Take a Detour for Some Cake

What kind of cake would you like? We have Flutter cake, Flutter cake, and Flutter cake. In other words, we have “The three layer Flutter cake”. Here are the three layers with a quick and (very) dirty explanation of what they are:

  • Widget Layer: In essence, it’s a Blueprint. The description in the docs will only confuse you until you’ve been doing Flutter for at least two months, so for now just tell yourself it’s a Blueprint. That’s close enough. The Widget Layer is the Blueprint you design in your code. It’s what you write.
  • Element Layer: The element layer is something that gets used to check each Widget during layout to see if it’s changed since the last frame. If it’s changed, it’s marked for destruction and garbage collection, then it gets rebuilt in the next layout pass. If it hasn’t changed, then it gets left alone and stays there. An Element is incredibly simple. It only contains the Type of your Widget and, if there is one, it’s unique identifier key. This key is usually assigned at random by the SDK. (Think of a key as the name of a named instance, for the Widget you used. We don’t usually use named instances in our Flutter code, but that doesn’t mean the SDK isn’t using them behind the scenes.)
  • Speaking of behind the scenes, the top layer of the Flutter Layer Cake is handled entirely behind the scenes. The Render Layer handles drawing to the canvas, and has its own ideas about what classes to use. It doesn’t use Rows or Columns, it doesn’t even use Flexes. It uses the RenderFlex, which is a different class that is much more than just a blueprint. This is a class that makes the magic happen. The Render Layer doesn’t care about the convenience of calling something a Row or Column; it just uses the RenderFlex and sets the direction property accordingly.

And that, my dear reader, is why your error says RenderFlex instead of Row or Column. The error isn’t happening when you layout the Row and Column in your Widget layer. Things don’t explode and catch on fire until you try to make things happen in the Render Layer, which means they don’t explode until runtime.

So the error is telling you the truth. It’s the RenderFlex in the Render Layer that’s blowing up, not the Row or Column that’s way back in the Widget Layer. This is also why the analyzer doesn’t catch the error in your IDE, because the error doesn’t occur until runtime.

Rows and Columns: Where Were We?

Ah yes, Rows and Columns. If you look at the constructor for the Flex class, you’ll see that mainAxisSize defaults to maximum:

Flex({
  Key key,
  @required this.direction,
  this.mainAxisAlginment = MainAxisAlignment.start,
  this.mainAxisSize = MainAxisSize.max, // <<<<<<< Default to max
  this.crossAxisAlignment = CrossAxisAlignment.center,
  this.textDirection,
  this.verticalDirection = VerticalDirection.down,
  this.textBaseline,
  List<Widget> children = const <Widget>[],
})

This can cause you quite a few problems, in various situations. Over time, some have found they prefer to set the mainAxisSize to MainAxisSize.min as soon as they create a Row or Column, before they do anything else.

A Quick Primer on Flexibles

In order to avoid a lot of confusion, there are two terms you need to understand clearly… Else you’ll be seriously confused.

  • Flex: The Superclass of Row and Column.
  • flex: The strength/weight setting of a Flexible

I know, don’t get me started; because if you confuse the Flex with the flex then you’re going to be Fluxed.

A Flexible can be thought of as a balloon that gets bigger or smaller depending on a few different things. The flex of a Flexible is kind of like how much air pressure it has. If there are two balloons (Flexibles) and one has twice as much air in it (the flex parameter), then that one will use up twice as much space as the other. That’s how much space gets used for the Flexible’s child.

But what if there are other things in the Row or Column that aren’t wrapped in Flexibles? How much space do they get?

They get whatever height or width you set for them. In fact, they get “head of the line” privileges. Meaning the amount of space that Flexibles have available do them is what’s left over after non-Flexibles have already gotten what they want. Think of it this way, Flexibles are the dogs at dinner. They get whatever is left over after everyone else eats whatever they want.

Flexible is the Superclass of Expanded, which a Flutter developer uses all the time. And a Spacer uses an Expanded under the hood.

Image Auto-Resize Tip Using Expanded

When you need an image to change its size relative to what’s going on around it, an easy way to do this is to wrap it in an expanded, which is then in a Row or Column. This way, the image will adapt to use whatever space is available. You control the size of the image by placing at least one Spacer or Expanded in the Row or Column and then controlling their relative sizes by adjusting their flex properties.

In this case, the Spacers are used to adjust the vertical placement of the Image inside the Column. Since Spacer uses Expanded under the hood, it has a flex, too.

So, basically we have three balloons, and one of them contains an Image. The caption and the SizedBox won’t change size, everything is controlled by adjusting the imageResizeAdjustment variable. If you make it small, the Image’s height will shrink and this causes the width to shrink as needed in order to maintain the Image’s aspect ratio. The same holds true for if you increase imageResizeAdjustment. Once this Image is full-width, it can’t get any larger.

Flutter Rows and Columns: What to do When it Goes Wrong

Dealing with the problems that come with Rows and Columns can be one of the most frustrating things in Flutter. Even people who have been working with Flutter since early Alpha occasionally still run into situations that leave them scratching their heads. Here are a couple of the main ones you might see, and what you can do about them:

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

Some of us call this the Boogeyman of Flutter. It comes crawling out from under your bed and sometimes it will show up even when you thought you had done everything to prevent it. It means there’s an Expanded inside the Row or Column (child has a flex higher than 0) but there is nothing outside of it to keep it from expanding to infinity (there weren’t any Constraints passed down to it).

问题的根源是在 Row / Column 的含有可扩展的子组件。

The reason some Rows and Columns don’t get Constraints passed in is because there are some situations where you want to let the children in the Row or Column decide how big it should be. You just add up the sizes of the children. This works fine as long as all of the children have specified sizes, but it explodes in a fireball if one of the children is trying to expand to infinity.

当 Row / Column 的 mainAxisSizemax,而某个子组件期望扩展到无限大的时候,问题就会发生。

It can happen if you have a child that will try to be as big as possible; like another Row or Column nested inside of the one with the error, anything with its size along the main axis set to double.infinity or even an empty Container (remember, an empty Container tries to be as big as possible).

The quick fix is to change the mainAxisSize of your Flex to MainAxisSize.min (remember that from above?) and then wrap the offending child in an Expanded.

解决的办法是把 mainAxisSize 设置成 min,然后将需要扩展的子组件用 Expanded 包裹住。

“BoxConstraints forces an infinite height (or width).”:

This is only a slightly different situation than the one above. One way this can happen is if your Row or Column has a grandchild (a child of a child) with its height/width to double.infinity and the child isn’t limiting the grandchild’s size.

If the problem is in the mainAxis, then you can fix it by putting the child (that owns the grandchild) inside of an Expanded.

But if the issue is along the crossAxis then you probably nested Rows and Columns inside of each other, so you need to put the nest_ed _Row or Column into an Expanded instead.

Wrapping Up

I know that might seem like a lot, but it is. Seriously though, this is just enough to get you going. If you really want to know more about Containers, Rows and Columns then I suggest you read a couple of my other articles over at Flutter Community Medium:

The Deep Dive is a four-part series, don’t try to read it all in one day!

If you have any other questions, feel free to ask!

Happy Fluttering!