Specific min and max size for expanded widgets in Column - flutter

I have a Column with a set of Expanded widgets.
Is there a way to control the range in which they expand? I want one widget to expand only to a certain size and make the rest available to other widgets.
EDIT:
Because I got two probably misleading answers, I’d like to clarify. I want something like this:
Expanded(flex: 1, minSize: 50, maxSize: 200, child: ...)
That means that this expanded widget takes a flex of 1, but should never be smaller than 50 and bigger than 200.

When using ConstrainedBox in Rows my minWidth is ignored and the maxWidth is used as a fixed size.

You are looking for ConstrainedBox.
You can create a List of Widgets with both ConstrainedBox and Expanded, as following:
Row(
children: [
ConstrainedBox(
child: Container(color: Colors.red),
constraints: BoxConstraints(
minWidth: 50,
maxWidth: 100,
),
),
Expanded(
child: Container(color: Colors.green),
),
Expanded(
child: Container(color: Colors.blue),
),
],
),

As far as I know, there's no elegant pre-built way in Flutter to do this.
The answer by #HugoPassos is only partially complete. A ConstrainedBox will not change its size unless its content changes size. I believe what you're looking for is for the box to be say 1 / 4 of the width of row if 1/4 of the row is greater than the min and higher than the max.
Here's a working main.dart that get's the job done with width in a row, though you could just as easily use height in a column:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({required this.title});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: LayoutBuilder(builder: (context, constraints) {
return Center(
child: Row(
children: [
ConstrainedWidthFlexible(
minWidth: 50,
maxWidth: 200,
flex: 1,
flexSum: 4,
outerConstraints: constraints,
child: SizeLogger(
child: Container(
color: Colors.red,
width: Size.infinite.width,
height: Size.infinite.height,
child: Text('click me to log my width')),
),
),
Flexible(
flex: 1,
fit: FlexFit.tight,
child: Container(color: Colors.green),
),
Flexible(
flex: 2,
fit: FlexFit.tight,
child: Container(color: Colors.blue),
),
],
));
}));
}
}
class SizeLogger extends StatelessWidget {
final Widget child;
SizeLogger({required this.child});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => {print('context.size!.width ${context.size!.width}')},
child: child);
}
}
class ConstrainedWidthFlexible extends StatelessWidget {
final double minWidth;
final double maxWidth;
final int flex;
final int flexSum;
final Widget child;
final BoxConstraints outerConstraints;
ConstrainedWidthFlexible(
{required this.minWidth,
required this.maxWidth,
required this.flex,
required this.flexSum,
required this.outerConstraints,
required this.child});
#override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(
minWidth: minWidth,
maxWidth: maxWidth,
),
child: Container(
width: _getWidth(outerConstraints.maxWidth),
child: child,
),
);
}
double _getWidth(double outerContainerWidth) {
return outerContainerWidth * flex / flexSum;
}
}

In short: there is no simple answer without calulating the size.
First you need to know: Widget with Size dominate the avialable size in Row/Column, then Flexiable/Expanded share the remaining space.
Column(
children:[
Flexiable(...
Expanded(...
SizedBox(... // <- dominate the avialable size first
]
)
And the parent widget dominate the size of the child widget:
Column(
children:[
Flexiable(flex: 1),
Flexiable(
flex: 1,
child: SizedBox(... // size can't be larger than 1/2
]
)
It is the choise problem if the size exceed or insufficient. I can show some simple examples below:
(BTW: I replace ConstraintedBox with SizedBox because we only use maxWidth/maxHeight. check Understanding constraints)
Flex with max size
In this case is simple and can use only Flexible + SizedBox
Row(
children: [
Flexible(flex: 1, child: _textWidget('Flex:1')),
Flexible(
flex: 1,
child: SizedBox(
width: 300,
child: _textWidget('Flex: 1, max: 300'),
),
),
],
),
Flex with min/max size
For the case need the total size(from LayoutBuilder) and the percentage of the widget size.
LayoutBuilder(
builder: (context, constraint) {
final maxWidth = constraint.maxWidth;
return Row(
children: [
Flexible(flex: 1, child: _textWidget('Flex:1')),
SizedBox(
width: (maxWidth / 3).clamp(200, 300),
child: _textWidget('Flex:1, min: 200, max: 300'),
),
SizedBox(
width: (maxWidth / 3).clamp(200, 300),
child: _textWidget('Flex:1, min: 200, max: 300'),
),
],
);
}
)
Code Example
https://dartpad.dev/?id=f098f9764acda1bcc58017aa0bc0ec09

Yes! There is a way to control maxHeight and maxWidth inside a Row or Column (unbounded Widgets). You could use the Widget LimitedBox in which your maxHeight and maxWidth parameters only works inside unbounded Widgets.
https://api.flutter.dev/flutter/widgets/LimitedBox-class.html
Column(
children: [
LimitedBox(
maxHeight: 200,
maxWidth: 200,
child: Container(),
)
],
),

This worked for me. Please, Check it out.
Expanded(
child: Align(
alignment: Alignment.topCenter,
child: Container(
child: ConstrainedBox(
constraints:
BoxConstraints(maxHeight: 500),
child: Container(
child: DesiredWidget(),
),
),
),
),
)
Instead of directly expanding the desired widget, you should expand and align a container, then set the constrainedbox as a child of the container and then insert the desired widget as a child of the constrainedbox.
This way i managed to render the widget precisely as big as it needs to be, but never exceeding 500 height.

You can use constraint box to use the range of min and max width like below:
Row(
children: <Widget>[
Text("Text 1"),
ConstrainedBox(
constraints: BoxConstraints(maxHeight: 30, maxWidth: 40, minWidth: 30),
),
Text("Text 2")
],
)

Related

Widget taking width of grandparent

The CircularProgressIndicator widget is taking width of its grandparent widget, the SizedBox of width 200. I'm expecting the dimension to be 10x10, but the dimensions are 200x10. I get the same behavior if the innermost widget is a box drawn with a Container and BoxDecoration.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Color.fromARGB(255, 18, 32, 47),
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const SizedBox(
width: 200,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()));
}
}
This is a simplified version of my app, and I need the outer SizedBox.
There are some limitation on Widget constrains.
If a child wants a different size from its parent and the parent doesn’t have enough information to align it, then the child’s size might be ignored. Be specific when defining alignment
A widget can decide its own size only within the constraints given to it by its parent. This means a widget usually can’t have any size it wants.
A widget can’t know and doesn’t decide its own position in the screen, since it’s the widget’s parent who decides the position of the widget.
readmore here: https://docs.flutter.dev/development/ui/layout/constraints
thats why, the second SizedBox size was ignored, because its doesnt
know the position and aligment. and because of that, the
CircularProgressIndicator() take the grandparent size.
Solution:
set the alignment to the SizedBox.
const SizedBox(
width: 200,
child: Align(alignment:Alignment.center,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator())));
The short answer and solution is just wrap your inner SizedBox with Center. It will help.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(),
),
),
);
}
}
I recommend you read this article which describes how sizes and constraints work in Flutter. https://docs.flutter.dev/development/ui/layout/constraints
You can use Center widget to centralize the child widget
SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()),
));
Or you can use Align widget to re-position the child in the available area
SizedBox(
width: 200,
child: Align(
alignment: Alignment.topRight,
child: SizedBox(
width: 10, height: 10, child: CircularProgressIndicator()),
));
Remove the outer SizedBox which is having the width of 200.
or use like this.
const SizedBox(
width: 200,
child: Center(
child: SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(),
),
),
);

How to remove padding from some children in a Column?

Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
// ... some widgets
Padding(
padding: const EdgeInsets.all(-20.0), // Error: How to do something like this?
child: FooWidget()
),
// ... more widgets
BarWidget(), // Remove padding from here also
// ... and some more widgets
],
),
)
I'm providing a padding of 20 to my Column, but I want to remove this padding from some of its children, like FooWidget, BarWidget, etc. How can I do that?
Note: I'm not looking for workarounds like provide the padding to other widgets instead of the root Column or wrap those widgets in another Column and provide that column a padding, etc.
you can apply transformation to the widgets that you want to remove the padding for, for example:
Container(
transform: Matrix4.translationValues(-20.0, 0, 0.0), //here
child: FooWidget()),
This working solution uses UnconstrainedBox that only takes away the left side and right side of padding. You might do the calculation of overflowWidth first when screenWidth is not feasible to use.
In addition, this comes up with a RenderConstraintsTransformBox overflowed exception that will be gone away in app release version, e.g. flutter build appbundle for android app.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: UnboundedWidget(),
);
}
}
class UnboundedWidget extends StatelessWidget {
const UnboundedWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final double overflowWidth = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: const Text('Unbounded demo'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
UnconstrainedBox(
child: Container(
color: Colors.red,
width: overflowWidth,
child: const Text('123'),
),
),
],
)),
);
}
}
There is no such thing as negative margins in flutter.
You can try workarounds with transforms on x, y, z axis as transform property on Container widget.
Or try with SizedBox which ignores parent padding.
Here is a similar example that should work:
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Container(
width: double.infinity, height: 20, color: Colors.green),
// This child ignores parent padding.
SizedBox(
width: MediaQuery.of(context).size.width,
height: 20,
child: Expanded(
child: OverflowBox(
maxWidth: MediaQuery.of(context).size.width,
child: Container(
width: double.infinity,
height: 20,
color: Colors.red)),
),
),
Container(
width: double.infinity,
height: 20,
color: Colors.blue),
],
),
),
Use Stack widget with Positioned widget Positioned(left: -20, child: widget)
on the other hand, for padding less.
you can create custom 2 widget name as:
paddingLessWidget(child: your widget)
paddingWithWidget(child: your widget)
then use this into column() widget.
Remove padding from column's parents.
use as:
Column(
children:[
paddingLessWidget(child: your widget),
paddingWithWidget(child: your widget)
]
),

Best way to define a bespoke Card in Flutter

I've been attempting to define a bespoke Card in Flutter using row and column and cannot seem to get a fixed format layout similar to the image above (the red lines denote the areas of the card and are just there to show the areas).
e.g.
return Card(child: Column(
children: [
Row(
children: [
Column( children: [
Text('Riverside cafe...'),
Ratingbar(),
],),
ImageWidget(),
],
),
Container(child: Text('Pubs & restaurants'), color : Colors.purple)
],
The resulting cards are to be displayed in a listview and using rows and columns above results in the areas being different sized depending on the data.
It seems to me that using row and column may not be the best way to achieve this. Is there a better way?
As for the best, I suppose that's for you and your client to decide.
For as long as I've been working with Flutter, I haven't come across anything like CSS grid which is great for situations like this. The closest comparison is StaggeredGrid (https://pub.dev/packages/flutter_staggered_grid_view) but that doesn't offer as much control as CSS grid and doesn't seem to quite fit your use case.
Rows, Columns (and other layout widgets) can get the job done:
Here's the main.dart that produced the above example. Code quality isn't perfect, but hopefully you can follow it well enough and it helps you get done what you need to get done.
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
static const String _title = 'Bespoke card example';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bespoke card example')),
body: Center(
child: Wrap(runSpacing: 10.0, children: [
BespokeCard(title: 'Short name', width: 350),
BespokeCard(
title: 'Riverside Cafe with a really long name', width: 350)
]),
),
);
}
}
class BespokeCard extends StatelessWidget {
final String title;
final double width;
BespokeCard({this.title, this.width});
#override
Widget build(BuildContext context) {
Widget _restaurantNameContainer = Container(
constraints: BoxConstraints(
minHeight: 0,
maxHeight: 120,
maxWidth: (500.0 - 40 - 175 + 1),
minWidth: (500.0 - 40 - 175 + 1),
),
child: AutoSizeText(
title,
style: TextStyle(fontSize: 60),
maxLines: 2,
minFontSize: 10,
stepGranularity: 0.1,
overflow: TextOverflow.ellipsis,
),
);
Widget _rightSideSection = Container(
width: 175,
height: Size.infinite.height,
child: Center(
child: Icon(
Icons.umbrella,
size: 70,
),
),
);
Widget _topSection = Flexible(
flex: 1,
child: Row(
children: [
Flexible(
fit: FlexFit.tight,
flex: 3,
child: Padding(
padding: EdgeInsets.only(left: 40.0, top: 25.0),
child: Column(
children: [
Flexible(child: Container(), flex: 1),
_restaurantNameContainer,
Text('* * * * *', style: TextStyle(fontSize: 70)),
],
),
),
),
_rightSideSection
],
),
);
Widget _bottomSection = Container(
height: 70,
width: Size.infinite.width,
child: Center(
child: Text('Pubs & Restaurants',
style: TextStyle(color: Colors.white, fontSize: 40)),
),
color: Colors.purple);
Widget unfittedCard = Card(
child: SizedBox(
width: 500,
height: 300,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [_topSection, _bottomSection],
),
));
return Container(
width: this.width,
child: FittedBox(fit: BoxFit.fitWidth, child: unfittedCard));
}
}
NOTES:
Be aware of flexFit (tight or loose) property: https://api.flutter.dev/flutter/widgets/Flexible/fit.html
You can either define fixed ratios with all flexibles, or you can mix Flexibles with Containers / SizedBoxes what have you
The package auto_size_text is great for situations like this. (Add auto_size_text: ^2.1.0 to your dependencies)
Be aware of box constraints. I needed them to make the title autosizing text be able to grow tall without also sitting in a large container.
Fitted box is really handy and makes scaling very easy in flutter.

ListView in LimitedBox vs ConstrainedBox

While trying to figure out how to limit the size of a ListView, I saw examples in Stackoverflow suggesting that doing something like placing the ListView in a LimitedBox and setting the maxHeight of the Limited box and the shrinkwrap of the ListView, would accomplish it. However, the ListView would still grow past the maxheight to accommodate the size of the children in the ListView.
Eventually, I tried an example using ConstrainedBox and setting it's maxHeight and the Shrinkwrap of the ListView; doing this seemed to accomplish the intended result of limiting the Listview height to the maxHeight setting.
Can someone help me understand why LimitedBox maxHeight does not work in this case, when ConstrainedBox does?
Here is an example of the LimitedBox that is not constraining itself to the maxHeight:
Widget _getPopupCard(){
return Hero(
tag: 0,
createRectTween: (begin,end){
return MaterialRectCenterArcTween(begin: begin, end: end);
},
child: Container(
child:Padding(
padding: EdgeInsets.all(14),
child: Material(
borderRadius: BorderRadius.circular(18),
color: Colors.white,
child: LimitedBox(
maxHeight: 300,
child: ListView(
shrinkWrap: true,
children: _getTiles(),
),
),
),
),
),
);
}
Here is an example of the ConstrainedBox version that does limit itself to the maxHeight:
Widget _getPopupCard(){
return Hero(
tag: 0,
createRectTween: (begin,end){
return MaterialRectCenterArcTween(begin: begin, end: end);
},
child: Container(
child:Padding(
padding: EdgeInsets.all(14),
child: Material(
borderRadius: BorderRadius.circular(18),
color: Colors.white,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 300.0),
child: ListView(
shrinkWrap: true,
children: _getTiles(),
),
),
),
),
),
);
}
https://api.flutter-io.cn/flutter/widgets/LimitedBox-class.html
LimitedBox : A box that limits its size only when it's unconstrained.
ConstrainedBox : which applies its constraints in all cases, not just when the incoming constraints are unbounded.
LimitedBox can only works when parent is unconstrainted and flutter cannot render infinity width/heigh, so LimitedBox must both provide MaxWidth and MaxHeight
You can wrap UnconstrainedBox to make LimitedBox works, but you really don't need this... see: https://www.woolha.com/tutorials/flutter-using-limitedbox-widget-examples
https://dartpad.dev/2cfc997a7984bf848687f209cba917ce?null_safety=true
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'LimitedBox',
home: Scaffold(
appBar: AppBar(
title:
Text('LimitedBox can only work when parent is unconstrainted')),
body: Center(
child: UnconstrainedBox( // wrap UnconstrainedBox to make LimitedBox works, but you really don't need this... https://www.woolha.com/tutorials/flutter-using-limitedbox-widget-examples
child: _getPopupCard(),
),
),
),
);
}
}
Widget _getPopupCard() {
return LimitedBox(
maxHeight: 300.0,
maxWidth: 300.0,
child: ListView(
shrinkWrap: true,
children: _getTiles(),
),
);
}
List<Widget> _getTiles() {
return List.generate(100, (int i) => i).map((int i) {
return Container(
color: i % 2 == 0 ? Colors.red : Colors.green,
child: Center(child: Text("$i")),
);
}).toList();
}
update:
Is Container the constrained parent that is overriding the LimitedBox even though I didn't set height or width on the Container?
I search this question just because I'm reading this guide: https://flutter.dev/docs/development/ui/layout/constraints
Container's layout behavior is little complex.
About this, I've run code below , seems flutter will give Container a default Constraints. The Container source tells something, maybe you should get some info from the source.
runApp(
MaterialApp(
home: UnconstrainedBox(
child: LayoutBuilder(
builder: (ctx, Constraints constraints) {
print(ctx);
print(constraints);//BoxConstraints(unconstrained)
return Text("hello world");
},
),
),
),
);
runApp(
MaterialApp(
home: Container(
color: Colors.white,
child: LayoutBuilder(
builder: (ctx, constraints) {
print(ctx);
print(constraints); // BoxConstraints(w=500.0, h=296.0)
return Text("hello world333333");
},
),
),
),
);

ListView or SingleChildScrollView of variable size

I want to have a widget of variable height that contains a ListView or a SingleChildScrollView or anything that scrolls.
I tried making it like this:
Container(
color: Colors.pink,
child: Column(
children: [
Container(color: Colors.orange, child: Text("Header")),
SingleChildScrollView(
child: Container(
height: 10000,
color: Colors.green,
child: Text("the height of this content could be anything")),
),
Container(color: Colors.blue, child: Text("Footer")),
],
),
)
This causes an overflow because the SingleChildScrollView expands to height of 10000 pixels. If I enclose it in an Expanded then it works fine but then if its child's height is for example 200 instead of 10000, it will still expand the parent widget to the entire height of the screen.
Is it possible to have the height of the scroll/list adjust itself to its content and only expand to the entire screen if it needs to?
You can do it if you know the size of the footer and header widget and using LayoutBuilder widget to get the constraints.
#override
Widget build(BuildContext newcontext) {
return Center(
child: Scaffold(
body: Container(
color: Colors.pink,
child: LayoutBuilder(
builder: (_, constraints) {
final sizeHeader = 150.0;
final sizeFooter = 150.0;
final sizeList = 1000.0;
final available =
constraints.maxHeight - (sizeHeader + sizeFooter);
Widget _buildCenterWidget() {
return Container(
height: sizeList,
color: Colors.green,
child: Text("the height of this content could be anything"),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: sizeHeader,
color: Colors.orange,
child: Text("Header")),
available < sizeList
? Expanded(
child: _buildCenterWidget(),
)
: _buildCenterWidget(),
Container(
height: sizeFooter,
color: Colors.blue,
child: Text("Footer")),
],
);
},
)),
),
);
}
You can use ConstrainedBox, to specify minHeight, maxHeight for your widget. Remember that none of your widget should have infinite height/width, that spoils the UI, may also throw error