Responsiveness is very important when building mobile apps, your UI needs to be able to adapt to different screen sizes without elements looking stretched or out of place.
Flutter provides several useful widgets to tackle this issue, in this article we will explore Flexible and Expanded and look at some common ways they can be used when building your Flutter apps.
The Flexible
widget lets you scale the size of your elements based on the size of their parent, this is a very easy way of sizing children as a percentage of their parents.
Let’s start by rendering a simple column that has 3 children:
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: SizedBox(
height: 400,
child: Column(
children: [
Container(
height: 100,
width: 100,
color: Colors.red,
),
Container(
height: 100,
width: 100,
color: Colors.blue,
),
Container(
height: 100,
width: 100,
color: Colors.purple,
),
],
),
),
),
),
);
}
}
Using just the given widgets we would need to use math to add percentage based heights:
@override
Widget build(BuildContext context) {
double containerHeight = 400;
return Scaffold(
body: SafeArea(
child: Center(
child: SizedBox(
height: containerHeight,
child: Column(
children: [
Container(
height: containerHeight * 0.2,
width: 100,
color: Colors.red,
),
Container(
height: containerHeight * 0.2,
width: 100,
color: Colors.blue,
),
Container(
height: containerHeight * 0.6,
width: 100,
color: Colors.purple,
),
],
),
),
),
),
);
}
}
While this is a perfectly fine approach if your UI is this simple, but in actual practice you will end up with several such elements that need to scale with the size of their parent.
It is generally a good idea to keep computations in your build method to a minimum, also it is always a good idea to off-load such computations to the framework rather than having to manually do them. This is where Flexible comes in!
The Flexible widget scales its size based on available space, so for example if you change the above code to wrap the last container with a Flexible and not provide any height, it would result in the same output. You can leverage this behaviour to scale all the children based on the value of flex
provided to the Flexible
widget.
@override
Widget build(BuildContext context) {
double containerHeight = 400;
return Scaffold(
body: SafeArea(
child: Center(
child: SizedBox(
height: containerHeight,
child: Column(
children: [
Flexible(
flex: 2,
child: Container(
width: 100,
color: Colors.red,
),
),
Flexible(
flex: 2,
child: Container(
width: 100,
color: Colors.blue,
),
),
Flexible(
flex: 6,
child: Container(
width: 100,
color: Colors.purple,
),
),
],
),
),
),
),
);
}
}
The size of the Flexible is determined by the value of flex
, with larger flex
values taking more space than others. If all children have a flex value then the entire space provided will be divided in percentages based on the value. In the example above we want the total of flex values to be 10 to make dealing with percentages easier (6 = 60%, 2 = 20% and so on), the output will be the same as the example where we calculate the height of each container.
Pro Tip: The value of flex
cannot be in decimals, so a total value of 10 might make it hard to have finer control over percentages (33% for example). If you make sure the total of all flex values is 100 you can get exact percentage values for the size of your widgets.
Good question anonymous stranger! It is very common to have your UI designed in a way where you have fixed values for some of your widgets and then have the rest of your widgets sized based on available space. Flexible makes this really easy!
@override
Widget build(BuildContext context) {
double containerHeight = 400;
return Scaffold(
body: SafeArea(
child: Center(
child: SizedBox(
height: containerHeight,
child: Column(
children: [
Container(
width: 100,
height: 170,
color: Colors.red,
),
Container(
width: 100,
height: 170,
color: Colors.blue,
),
Flexible(
child: Container(
width: 100,
color: Colors.purple,
),
),
],
),
),
),
),
);
}
Flexible makes it so that the last container takes the remaining space.
Now that we know how the Flexible widget works, theres another useful widget that can serve as an alternative. Flexible widgets have rules and an order to how widgets are laid out, if your UI is relatively simple it can be more effective to skip those layout steps and calculations. For example if you just needed one Flexible widget to take up remaining space in your screen, you don’t really need the framework to go through all its layout rules.
Instead use the Expanded widget!
The Expanded widget simply takes all available space. One important distinction between the Flexible and Expanded widgets is that Flexible does not force its children to take the same size as itself where the Expanded widget forces its child to take the same size.
@override
Widget build(BuildContext context) {
double containerHeight = 400;
return Scaffold(
body: SafeArea(
child: Center(
child: SizedBox(
height: containerHeight,
child: Column(
children: [
Container(
width: 100,
height: 100,
color: Colors.red,
),
Expanded(
child: Container(
width: 100,
height: 170,
color: Colors.blue,
),
),
],
),
),
),
),
);
}
The Spacer widget is similar to the Expanded widget in that it takes up all available space. The key difference is that it doesnt take a child, it just takes up space. So if your UI requires empty space between elements, Spacer is the way to go.
Flexible and Expanded are widgets you will use a lot when building your apps (and if you don’t, you really should!). Thanks for reading!