Handling stack when the size of the children exceeds screen size - flutter

It is very easy to control the layout using Stack when the children's size is less than that of the screen. But when the children's height is greater than that of the screen size, you will need to use SingleChildScrollView. But if I use this, the whole screen goes white and produces the error shown below.
How can I handle the layout when using Stack & SingleChildScrollView in these circumstances.
Any help is greatly appreciated. Thank you.
Sample code
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: SingleChildScrollView(
child: Stack(
fit: StackFit.expand,
children: [
_Background(),
Positioned(left: 0, top: 100, child: WidgetA()),
Positioned(left: 0, top: 270, child: WidgetB()),
],
),
),
),
),
);
}
}
class _Background extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
);
}
}
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
height: randomHeight(),
width: MediaQuery.of(context).size.width,
color: Colors.purple,
child: Center(
child: Text('Widget A'),
),
);
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
height: 250,
width: MediaQuery.of(context).size.width,
color: Colors.yellow,
child: Center(
child: Text('Widget B'),
),
);
}
}
Error
════════ Exception caught by rendering library ═════════════════════════════════
RenderBox was not laid out: RenderRepaintBoundary#2e73f relayoutBoundary=up1 NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1940 pos 12: 'hasSize'
The relevant error-causing widget was
Scaffold
lib/main.dart:11
════════════════════════════════════════════════════════════════════════════════

Cause:
The SingleChildScrollView Provides Infinite Space.
The stack sizes itself to contain all the non-positioned children, which are positioned according to alignment (which defaults to the top-left corner in left-to-right environments and the top-right corner in right-to-left environments). The positioned children are then placed relative to the stack according to their top, right, bottom, and left properties.
The widget inflates forever.
It will also cause problems with the StackRender Algorithm as that will deal with the Zenith.
Read more in the api documentation here.
Solution give it a Constraint such as a SizedBox();

Wrap your stack with a container and define it's height and width with MediaQuery, then wrap the container with a column.

first the whole screen goes white because the StackFit.expand tell the stack to expand as much as possible and since it is inside SingleChildScrollView that is infinity and can't be handled.
second is that the stack height is defined based on its children and the Position widget height is not calculated , like css position:absolute; if you are familiar with that , so the remaining is your _Background whcih is the widget that define the height of the stack ..
so now SingleChildScrollView and its child has (screen height) height = no scrolling and to solve this we have to specify height enough for our positioned widgets to appear in and for our stack to be able to scroll in.
How to fix
first in your stack change fit to
child: Stack(
fit: StackFit.loose,
second your background which is responsible for the height as mentioned above
class _Background extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
height: MediaQuery.of(context).size.height + 250, // <-- change this
width: MediaQuery.of(context).size.width,
);
}
}

Related

how to give a widget, a specific width according to its parent - Flutter

there is a scenario that i have a circle and inside that circle i want to show some text. Both items are in a scrollable listview. i want to calculate the width of the parent and then give with to the inner children to be placed and shouldn't move backward or forward from its outer parent circle widget.
i have used stack widget to do so which works fine on a single device or screen but what about other screen sizes?
any suggestion according to this?
I asked yesterday the same concept question:
the FractionallySizedBox is super useful on those cases, it gives the width / height of a children based on the width / height of the parent:
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ParentCircle(
height: 250,
width: 250,
color: Colors.red,
child: FractionallySizedBox(
widthFactor: .5,
heightFactor: .5,
alignment: FractionalOffset.center,
child: ChildCircle(),
),
),
),
),
);
}
}
this will give the ChildCircle a 50% width and 50% height of the ParentCircle

Why does a SizedBox in another SizedBox ignore its width and hight?

When I nest two SizedBoxes, the width and height of the inner box are ignored. Why is this, how can I work around it?
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
width: 300,
height: 500,
child: SizedBox(
width: 200, height: 200, child: Container(color: Colors.green)),
));
}
}
In this example, I have a 300x500 sized box and an inner 200x200 SizedBox. In the picture you can see that the green box is the size of the outer SizedBox but should actually be a 200x200 square.
According to flutter documentation: If given a child, this widget forces it to have a specific width and/or height. These values will be ignored if this widget's parent does not permit them. For example, this happens if the parent is the screen (forces the child to be the same size as the parent), or another SizedBox (forces its child to have a specific width and/or height). This can be remedied by wrapping the child SizedBox in a widget that does permit it to be any size up to the size of the parent, such as Center or Align.
So wrapping the child with center would solve the problem:
Center(
child: SizedBox(
width: 300,
height: 500,
child: Center(
child: SizedBox(
width: 200, height: 200, child: Container(color: Colors.green)),
),
)),
The problem is, SizedBox can set widget size only within the constrains set by the parent. Many widgets, like Padding, want their child to occupy 100% of the space available to them. This makes sense, because if the child is smaller they wouldn't know where to put it.
If you want the child to be smaller than the parent you could use Center or Align, e.g. replace
I had the same issue and I solved my problem using the FractionallySizedBox class.
You can specify the suitable size using fractions of the above SizedBox as widthFactor and heightFactor:
Widget build(BuildContext context) {
return SizedBox.expand(
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 0.5,
alignment: FractionalOffset.center,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue,
width: 4),
),
),
),
);
}

How to get inside a widget the resulting size of itself shrinked by FittedBox

In the code below, a row of two 300x300 boxes (_Box) wrapped with FittedBox shrinks to fit in the screen. As a result, each box becomes smaller than 300x300.
However, if I get the width of a box in the build() method of _Box using RenderBox.size and LayoutBuilder(), the obtained size is 300.0 and Infinity respectively.
How can I get the actual displayed size?
I'd like to get it inside the _Box class, without it getting passed from the parent.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: FittedBox(
child: Row(
children: <Widget>[
_Box(Colors.red),
_Box(Colors.orange),
],
),
),
),
),
);
}
}
class _Box extends StatelessWidget {
const _Box(this.color);
final Color color;
#override
Widget build(BuildContext context) {
RenderBox renderBox = context.findRenderObject();
print(renderBox?.size?.width); // 300.0
return LayoutBuilder(
builder: (_, constraints) {
print(constraints.maxWidth); // Infinity
return Container(
width: 300,
height: 300,
color: color,
);
},
);
}
}
The Flutter constraints object is used to limit how large or small a widget can be rendered, and is usually never really used to get the current size of the widget.
The renderBox.size object is of Size class, and as a result it has both renderBox.size.width and renderBox.size.height as defined getters. Note that these values can only be set once the layout phase of the current view is over: see the findRenderObject() docs page.
This means that you will have to avoid calling findRenderObject() from the build() method. Instead you will have to define a callback function that must execute after the layout process is complete. You can do this using that have widgets that have callback functions like onTap or onSelected. How you implement this and finally get the actual layout size by running the callback function is totally dependent on your use case.
Further recommended reading:
DevelopPaper - Sample code for getting the width and height of screen and widget in Flutter
Flutter on Github - How to get a height of a widget? #16061
Rémi Rousselet's amazing answer explaining his workaround (using an Overlay widget)
I'll answer my own question, although it is not a direct answer.
I couldn't find a way to get the size shrinked by FittedBox, but I realised that I was able to get around it by using Flexible instead.
SafeArea(
child: Row(
children: const [
Flexible(
child: _Box(Colors.red),
),
Flexible(
child: _Box(Colors.orange),
),
],
),
);
#override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 300.0),
child: AspectRatio(
aspectRatio: 1.0,
child: ColoredBox(color: color),
),
);
}
It still seems impossible to get the size via RenderBox, and it is now possible with LayoutBuilder. But either way, I didn't need them.
The constraints of the two boxies are shrinked by Flexible if a smaller space is available, but they expand as big as the space allows, so I limited the maximum size using ConstrainedBox and AspectRatio.
I didn't have to stick to FittedBox. I think I was obsessed with the idea of using it and couldn't think of other solutions when I posted the question two years ago.

Why doesn't Container respect the size it was given?

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: TestCode(),
);
}
}
class TestCode extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 100,
color: Colors.red,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
);
}
}
In fact, this code is very simple. I just want to display a 200 * 100 red cube and a 100 * 100 green cube.
But the running effect is full screen green? Why is that?
Next, I added a Scaffold toTestCode, as follows
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(body: TestCode()),
);
}
The effect again seems to be closer, showing a 200 * 100 green cuboid? Why is that?
Next, I added an alignment to the first Container, as follows
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topLeft,
width: 200,
height: 100,
color: Colors.red,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
);
}
Finally achieved the desired effect, why is this, who can explain, I must figure this out.
This is caused by what we call "tight vs loose constraints" in Flutter.
TD;DR, width/height are tight constraints (in the sense that there's only a single possibility).
But you never specified to the framework how to switch between the tight constraint of 200x200 specified by the parent, to the tight constraint of 100x100 of the child.
This cause a constraint conflict. Both widgets have a single possibility, and there's nothing that allows both to live together (like an alignment).
In that situation, the constraints of the parent always win, and we therefore end up with a 200x200 square where the child fills its parent.
If that is not what you want; then you should transform your "tight" constraint into a "loose" constraint.
A loose constraint is a constraint that offer the child multiple possibilities, which usually remove the conflict.
The most common way to introduce a loose constraint is to use Alignment (or Center or the alignment property of Container).
As such, if you write:
Container(
width: 200,
height: 100,
color: Colors.red,
child: Center(
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
);
then in that situation, Center will act as a middle ground between the parent and child Container.
It will understand that both wants a different size. And it will solve the conflict by aligning the child in the parent.
Now, why is this desired you may ask? Why can't Flutter implicitly add an alignment here?
That is because in many situations, this behavior is desired.
Basically, this ensures that there's always a way to customize the size of something (without having to expose tons of properties on all widgets).
Take RaisedButton as an example. It doesn't expose anything to change its size, but we may want it to fill the screen.
In that situation we'd write:
SizedBox.expand(
child: RaisedButton(...),
)
Because of the behavior we explained previously with the parent overriding the child size when there's a conflict, this code will produce a RaisedButton that properly fills the screen.
height and width properties getting overrided. You can have more info about box constraints on this article:
Dealing with box constraints
Flutter has bunch of layout widgets that can get the job done. In your case you gave Container to the home property of MaterialApp. This set the minimum size of the Container to the screen size. MaterialApp wants his child to fill all the screen in order to prevent any black pixels. This is an expected behaviour. However you can use a layout widget that can break this constraint, it may be Center , FittedBox or else.
An example with FittedBox:
class TestCode extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FittedBox(
fit: BoxFit.scaleDown,
child: Container(
width: 200,
height: 100,
color: Colors.red,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
),
);
}
}
Output:
Always remember that the container by default takes the dimensions of the parent, and it's a good practice to wrap all your elements/widgets in a root top level Container and then wrap each container in the widget tree with widgets that position the desired Container (Center can be such a widget)
An easy work-around solution for your problem would be:
// The root container
Container(
//Center for positioning the child Container properly
child: Center(
child: Container(
height: 100,
width: 200,
color: Colors.red,
//Center for positioning the child Container properly
child: Center(
child: Container(
height: 100,
width: 100,
color: Colors.green,
),
),
),
),
),
Just change your code into this. You have to specify alignment.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: TestCode(),
);
}
}
class TestCode extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
width: 200.0,
height: 100.0,
color: Colors.red,
alignment: Alignment.center, // where to position the child
child: Container(
width: 100.0,
height: 100.0,
color: Colors.green,
),
);
}
}

BoxConstraints forces an infinite height

child:Column(
children: <Widget>[
Container(
height: double.infinity,
width: 100.0,
color: Colors.red,
child: Text('hello'),
),)
in this,when i make height:double.infinity,it gives error in run saying **BoxConstraints forces an infinite height.**but when i give height manually it work fine.
can anyone explain me why this happening.
How about this one.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
children: <Widget>[
Expanded(
child: Container(
// height: double.infinity,
width: 100.0,
color: Colors.red,
child: Text('hello'),
),
),
],
),
),
);
}
}
This means that you can't offer inifite height to the container. It's obvious behaviour if you don't provide the contraints to height.
You have to specify limited height to the container so that flutter can render it, if you offer it infinite it how can flutter render that and up to which constraints it would do that !
Rather you can set double.infinity to width and flutter will successfully render that because by default flutter has constraints for width it will set width to width of screen.
Considering that you have to provide height as that of screen you can use MediaQuery for that
Widget yourMethod(or build)(BuildContext context){
final screenHeight = MediaQuery.of(context).size.height;
return Column(
children:<Widget>[
Container(
height:screenHeight,//but this will be height of whole screen. You need to substract screen default paddings and height of appbar if you have one
width:100.0,
....
)
]);
}
Hope this helps !
Happy coding..
BoxConstraints forces an infinite height
Why This Happens
You're asking to render an infinite height object without a height constraint... Flutter can't do that.
Column lays out children in two phases:
Phase 1: non-Flex items (anything not Expanded, Flexible or Spacer)
done in unconstrained space
Phase 2: Flex items (Expanded,Flexible, Spacer only)
done with remaining space
Phase 1
Column's phase 1 vertical layout is done in unbounded space. That means:
no vertical constraint → no height limit
any widget with infinite height will throw the above error
you can't render an infinite height object in an infinite height constraint... that's goes on forever
Phase 2
after Phase 1 widgets have taken as much space as they intrinsically need, phase 2 Flex items share the remaining/leftover space
the remaining space is calculated from incoming constraints minus Phase 1 widgets dimensions
double.infinity height will expand to use up the remaining space
Infinite Height is OK
Here's an example of using infinite height on a Container inside a Column, which is fine:
class ColumnInfiniteChildPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Flexible(
child: Container(
height: double.infinity, // ← perfectly fine
child: Text('Column > Container > Text')),
),
Text('Column > Text')
],
),
),
);
}
}
Remove the Flexible and the error will be thrown.