Flutter: Expanded vs Flexible - flutter

I've used both Expanded and Flexible widgets and they both seem to work the same.
What is the difference between Expanded and Flexible?

Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
Row(
children: <Widget>[
buildExpanded(),
buildFlexible(),
],
),
Row(
children: <Widget>[
buildExpanded(),
buildExpanded(),
],
),
Row(
children: <Widget>[
buildFlexible(),
buildFlexible(),
],
),
],
),
);

Expanded is just a shorthand for Flexible
Using Expanded this way:
Expanded(
child: Foo(),
);
is strictly equivalent to:
Flexible(
fit: FlexFit.tight,
child: Foo(),
);
You may want to use Flexible over Expanded when you want a different fit, useful in some responsive layouts.
The difference between FlexFit.tight and FlexFit.loose is that loose will allow its child to have a maximum size while tight forces that child to fill all the available space.

Widget under Flexible are by default WRAP_CONTENT although you can change it using parameter fit.
Widget under Expanded is MATCH_PARENT you can change it using flex.

Expanded - it is Flexible with set fit
class Expanded extends Flexible {
const Expanded({
Key key,
int flex = 1,
#required Widget child,
}) : super(
key: key,
flex: flex,
fit: FlexFit.tight,
child: child
);
}

You may use Flexible to resize the widgets in rows and columns. It's mainly used to adjust the space of the different child widgets while keeping the relation with their parent widgets.
Meanwhile, Expanded changes the constraints sent to the children of rows and columns; it helps to fill the available spaces there. Therefore, when you wrap your child in an Expanded widget it fills up the empty spaces.
Providing these videos from the Flutter's Official YouTube channel just to help out people, who might look for this in the upcoming future...
Expanded:
Flexible:

Expanded() is nothing more than Flexible() with
Flexible (fit: FlexFit.tight) = Expanded()
but, Flexible uses fit :FlexFit.loose by default.
FlexFit.tight = Wants to fit tight into parent taking as much space as possible.
FlexFit.loose = Wants to fit loose into parent taking as little space as possible for itself.

Expanded changes the constraints of a child widget so it fills any empty space. Expanded widget is a specialised Flexible widget with a set fit - Flexible(fit: FlexFit.tight. Expanded widgets also have a flex property.
Flexible makes the child widget flexible and resizable. You can add the flex or fit property to adjust the size and spacing.
Flexible fit properties include:
FlexFit.loose - The widget’s preferred size is used. (Default)
FlexFit.tight - Forces the widget to fill all of its extra space.

Flexible default will share the available space of the parent widget, but will NOT force the child to fit the space.
Expanded will share the available space of the parent widget, and force the child widget to change its width/height to fill the available space.
In fact, Expanded extends Flexible, which is a Flexible with FlexFit.tight. See the official document.
Here is a Container widget and three Flexible Widgets(flex = 1, fit = FlexFit.loose) in a row. We can see that the three flexible widgets share the same maxWidth (1/3 of the available screen width), and the blue one wants bigger than it, and the others want smaller. But as we can see, the blue guy has maxWidth as its width and the other widgets' width just fit their content.
Here is the code of the image above up:
Row(
mainAxisSize: MainAxisSize.max,
children: [
Container(
color: Colors.teal,
child: Text(
'Container Text ',
)),
Flexible(
child: Container(
color: Colors.blue,
child: Text(' Text.Flexible Text.Flexible Text.Flexible.')),
),
Flexible(
child: Container(
color: Colors.yellow, child: Text('Flexible Text.')),
),
Flexible(
child: Container(
color: Colors.lightGreen, child: Text('Flexible.')),
),
],
)

Related

Restricting the size of widgets programmatically

I'd like to create the layout shown in the picture below. This shows up fine:
Column( Row, ListView()))
While this renders errors:
Column( Row, Row( ListView(), Column ))
As soon as I replace the inner ListView() with Row( ListView(), Column ), nothing shows up and I get varying error messages, based on various changes I did.
Most often, I see a 'viewport has unlimited horizontal size'.
I guess Listview to be the source of the problem. That said, I'm no aware how to fix it.
Furthermore, I did find various hints on SO, but none fixed the problem.
How do I fix the problem?
Update 1
While the right Column() may be fixed width, the ListView() should take all remaining space of the screen [= availableWidth - WidhtOf(Column())].
put the ListView inside a Container And give it height/width
code
Column(
children: [
Row(
children: [],
),
Row(
children: [
Container(
height: 100,
width: 100,
child: ListView(
children: [
Text(
"1 C/G")
],
)),
Column(
children: [],
)
],
)
],
),
Both Row and Column have a mainAxisSizeparameter where you can set it to MainAxisSize.min so they will take their children's size.
Now, about the ListView, there is a shrinkWrap parameter that makes it so its max size will be its children's size. Another idea might be using a Flexible widget around the ListView, it does almost the same as Expanded, but the difference is: Expanded forces the child to occupy the remaining space, while Flexible allows the children to have any given size smaller than that, and that's because of its fit parameter that by default is FlexFit.loose.

How to make equal width or height constraints between widget in flutter?

In iOS's native AutoLayout constraints, It very easy to make a equal width constraints between ViewA and ViewB . And these two views will get a same width always.
But in flutter ,it seems can't be achived this kindof constraints so easily?
Do I have to make a SizedBox with specific width for both WidgetA and WidgetB explicitly?
You can always put your widgets in a Row(), and wrap every widget in Expanded(). This way they will always have 50% width of the parent Row(). And if you need to constrain the width, just wrap the Row() in a SizedBox().
SizedBox(
width: 500.0,
child: Row(
children: [
Expanded(
child: Container(),
),
Expanded(
child: Container(),
),
],
),
),

Wrapping Expanded Widget in Padding Widget

While learning flutter, I was trying to add some padding to an Image widget which was wrapped inside an Expanded widget, my approach was to wrap the Expanded widget with a Padding widget, but as a result the image actually overflowed.
When I changed the code to wrapping the Image widget inside a Padding instead, it worked fine. So my question here is, why did the image overflow when I used the Padding on the Expanded widget instead of the image ? Is it not possible to do that in Flutter ?
I am pretty sure your issue was not overflowing of the Expanded widget when you wrapped it with Padding, but rather Flutter threw an Incorrect use of ParentDataWidget exception. That is because widgets like Expanded and Flexible has to be direct children of Column, Row or Flex widgets. That is why the only solution is to use the Padding over the child of Expanded.
Summarising, this code will not work:
Row(
children: const [
Padding(
padding: EdgeInsets.all(8.0),
child: Expanded(
child: Icon(Icons.icecream_outlined),
),
),
Text("SomeTextyTextHere"),
],
),
it will not work because Expanded has to be a direct child of Row.
This code however will work fine:
Row(
children: const [
Expanded(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.icecream_outlined),
),
),
Text("SomeTextyTextHere"),
],
),

In Flutter, how can I have an adjusting vertical layout, a mix between column and listview behavior?

As far as I see, Column and ListView both have a very distinct usage when used for a base root layouting.
Column is used when the screen has few components (such as login screen). We can add some Expanded components to adjust white spaces in between, so when the keyboard is visible, the screen shrink to keep everything visible.
ListView is used when the screen has many components that potentially need scrolling. We can't use Expanded component in ListView. When using ListView, appearing keyboard does not change the white spaces, only change the size of outer ListView, while the inner content is wrapped in scroll view.
Now the problem is, how if I want to have screen like this:
When all the contents' combined vertical size is not longer than available height quota given from parent (in this case, screen's height), then the components behave like inside Column: expanding or shrinking to fill available white spaces according to rules set by Expanded.
When all the content's combined vertical size is longer than available height quota, then the components behave like inside ListView: all the possible expanding components will shrink into their minimum size (ignoring Expanded), and the screen is scrollable so user can see the rest of the screen below.
Is this possible to be done in Flutter? How?
EDIT: based on Reign's comment, I have isolated some code from SingleChildScrollView manual, but it looks like it still can't handle if its children contains Expanded.
Widget columnRoot({
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.spaceBetween,
AssetImage backgroundImage,
List<Widget> children
}) =>
LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) =>
SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: backgroundImage,
fit: BoxFit.cover),
color: Colors.white
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: mainAxisAlignment,
children: children
),
)
)
)
);
Widget content(BuildContext context) => columnRoot(children: [
Container(color: Colors.red, height: 100.0),
Expanded(Container(color: Colors.green)), // without this line, there's no layout error
Container(color: Colors.blue, height: 100.0),
]);
Error:
RenderFlex children have non-zero flex but incoming height constraints are unbounded.
I added some code you can test with also with some explanation.
Copy paste and run the code
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SingleChildScrollView( //Since setting it to scrollable, your widget Column with expanded children wont work as it supposed to be because it wont know its parent height
//Since its already scrollable `Expanded` will expand or shrink now based on it child widget (Expanded(child: SomeHeight widget)) refer: #10 example
child: IntrinsicHeight( //This will fix the expanded widget error
child: Container(
//Test remove this height
// height: 400, //But when you set its height before its parent scroll widget, `Expanded` will expand based on its available space
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: [
Container(color: Colors.red, height: 100.0),
//#10
//Experiment with this
Expanded(
child: Container(
color: Colors.purple,
// height: 100.0, //initialized height, remove parent container height: 400
// child: Text("This is also considered as min height"),
),
),
Container(color: Colors.blue, height: 100.0),
],
),
),
),
),
),
);
}
}

Flutter baseline text alignment with Wrap class

Using the Row class, I can make the text baseline aligned with this property:
crossAxisAlignment: CrossAxisAlignment.baseline,
But this property seems not to be available on the Wrap class.
The benefit of using Wrap instead of Row is that it allows having multiline text. While the Row class force the content to stay into one line.
How can I keep the benefit of Wrap, while making the text baseline aligned?
I also had this problem. There is an open issue in the Flutter Repo addressing this. While the built-in solution is not implemented yet, I did come up with a slightly-hacky workaround.
Basically, depending on your font and your needs...
Set your Wrap() to have crossAxisAlignment: WrapCrossAlignment.end,. Then you can make one of the children of your Wrap() a Column() that has a SizedBox() as the lowest child, with the height that you need to make it appear like your texts are using the does-not-exist WrapCrossAlignment.baseLine
Example of the problem:
Example of the solution as outlined above (I did point out that it's kind of hacky):
The Code:
class WrapBaselineHackWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(border: Border.all(color: Colors.red)),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.end,
spacing: 8,
children: [
Text(
'\$50,000.00',
style: TextStyle(fontSize: 24),
),
Column(
children: [
Text('Is a lot of money'),
//this is needed because Wrap does not have WrapCrossAlignment.baseLine
const SizedBox(height: 3),
],
),
],
),
);
}
}
Keep using Row and simply wrap Text widget with Expanded or Flexible widget.