ListView or SingleChildScrollView of variable size - flutter

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

Related

How to make the application responsive using Stack and Positoned - Flutter

How to use Stack and Positioned to add a shape in the SafeArea, I tried to change the color of the AppBar and connect to the shape and add mediaQuery, but still not on every screen it will be properly connected. So how to get a svg photo on the entire surface of SafeArea, and to make it responsive without using appbar, is it necessary to get the effect like in the picture below?(the code gives the effect as in the picture, but it is not responsive and consists of two parts, and I would like one part and get responsive)
Any help very much appreciated.
class Shape extends StatelessWidget {
static Route route() {
return MaterialPageRoute<void>(builder: (_) => Shape());
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
elevation: 0,
actions: [],
),
body: _profilePage(context),
);
}
Widget _profilePage(BuildContext context) {
return SafeArea(
child: Align(
child: Center(
child: Stack(
children: <Widget>[
Positioned(
width: MediaQuery.of(context).size.width * 1,
height: MediaQuery.of(context).size.height,
bottom: MediaQuery.of(context).size.width * 0.6,
child: _curved(context),
),
],
),
),
),
);
}
Widget _curved(BuildContext context) {
return SvgPicture.asset(
'assets/images/shape_purple.svg',
color: Colors.blue,
allowDrawingOutsideViewBox: true,
);
}
Use FitteBox Widget instead
FittedBox(
child: Image.asset('assets/images/background.png'),
fit: BoxFit.fill,
// decoration: BoxDecoration(
// color: Colors.white),
),

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");
},
),
),
),
);

Let wrap take full width

How can I make the red box to use full width and put the button to the very right of the screen when wrapping?
Also I want spaceBetween if the button is not wrapping, but it does not work:
This is what I have sofar:
Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Container(
color: Colors.red,
child: Wrap(
alignment: WrapAlignment.end,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Please wrap on small screens",
style: Theme.of(context).textTheme.bodyText1,
),
),
),
TextButton(
onPressed: () {},
child: Text("Bearbeiten"))
],
),
),
),
// ...
]
)
Updated
Finally, I manage to solve your problem. Probably is not a canonic way, but it's working.
You need to compute the total width of your content in order to compare it with the width of the screen (the width is the width of the widgets, plus space you want between widgets, plus padding from both sides).
With that, you are able to know when you need to change the layout, and you can apply the layout you want to each case.
Here I leave the code:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey textKey = GlobalKey();
final GlobalKey buttonKey = GlobalKey();
double widthLimit = double.infinity;
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
final textBox = textKey.currentContext.findRenderObject() as RenderBox;
final buttonBox = buttonKey.currentContext.findRenderObject() as RenderBox;
widthLimit = textBox.size.width + buttonBox.size.width + 8 + 16 * 2;
});
}
Widget _wrap(bool spaceBetween, Widget child) => spaceBetween
? child
: Align(
alignment: Alignment.centerRight,
child: child,
);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
width: double.infinity,
padding: EdgeInsets.all(16.0),
color: Colors.red,
child: Builder(builder: (context) {
final screenWidth = MediaQuery.of(context).size.width;
final spaceBetween = screenWidth >= widthLimit;
return Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.spaceBetween,
children: [
Text("Please wrap on small screen small screen", key: textKey),
_wrap(
spaceBetween,
TextButton(
key: buttonKey,
onPressed: () {},
child: Text("Bearbeiten"),
)),
],
);
}),
),
),
);
}
}
Old Answer
I am going to take a couple of premises from your screenshots:
The Wrap doesn't have to take the full width by default
You only want to go to two lines when there is not enough space in the device screen.
With that, you can make what you want with this code:
Container(
padding: EdgeInsets.all(16.0),
color: Colors.red,
child: Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.end,
children: [
Text("Please wrap on small screen"),
TextButton(onPressed: () {}, child: Text("Bearbeiten")),
]),
),
I added the spacing property to the Wrap to provide space as you wanted when both widgets are at the same line. Also, I set the alignment of the Wrap to end to align as you want.
When there is not enough space on the screen, the widget is going to move to a new line and align to the end. As the first widget is longer than the wrapped one and, as I said in the premises, we don't need to take the full width of the screen, Wrap will adapt to keep things aligned properly.

How to get the height of ListView.builder() widgets to be the height of the entire stack?

I have a ListView.builder() inside a column, and I would like to tell the LisView.builder to set the height according to the height of the widget that it will be returning from the builder method. How do I do that?
Wrap your ListView in a SizedBox with some fixed height. Setting to children height is impractical since Flutter would have to check the size of every element in the list and calculate the maximum.
I am not sure but looking at your question it looks like you want to render listview within the sizes which child widgets are taking
For that we can assign mainAxisSize: MainAxisSize.min, to column and shrinkWrap: true to Listview.builder
class _MyHomePageState extends State<MyHomePage> {
List<String> itemsList = [
"item0",
"item1",
"item2",
"item3",
"item4",
"item5"
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ListView Sample"),
),
body: Container(
color: Colors.black54,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListView.builder(
shrinkWrap: true,
itemCount: itemsList.length,
itemBuilder: (context, index) {
return Container(
child:
Align(
alignment: Alignment.centerRight,
child:Text(
itemsList[index],
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),),
width: 100,
height: 20,
margin: EdgeInsets.only(bottom: 10),
color: Colors.red);
}),
],
),
),
);
}
}
You can use global key to get height of list view or column,
But the height can be fetched only after a build is completed
//user gloabl key to get height of list view or column
GlobalKey key = GlobalKey();
SingleChildScrollView(
child: Column(
key: key,
children: [...]
)
//here you can get widget height
double totalWidgetHeight = key.currentContext!.size!.height;

Specific min and max size for expanded widgets in Column

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")
],
)