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

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

Related

How to control size of a Column inside a SizedBox

I am building a widget to display a chart/graph inside a fixed size window. The chart will be bigger than the window so the solution includes the user being able to scroll the graph widgets around inside a window (you can find details about this in an earlier question I asked about this here).
Part of the layout includes a fixed sized panel created using a SizedBox and, within that, a Column containing rows of widgets that make up the graph. I need the Column to fit its contents tightly so that I can track it's size and, for example, stop the user scrolling up when the last row is visible at the bottom of the SizedBox.
When the size of the children in the Column should make the Column smaller than the SizedBox, the Column is still being forced to be the size of the SizedBox. This is explained in the Flutter documentation here.
According to the Flutter documentation, the solution is:
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.
I have tried this and it doesn't seem to work. Below is a test app I wrote on DartPad to check this out. If I use either Align or Center as child of SizedBox and parent of the Column widget, the Column is still the same size as the SizedBox. I have also added MainAxisSize.min to the Column, but this doesn't appear to make any difference.
I have considered doing this using a Stack so that the Column is displayed over the SizedBox, rather than as a child of it, but that feels like a bit of a hacky workaround given that the documentation suggests you can control the size of a Column inside a SizedBox.
Does anyone know how I force the Column to be the smallest size it can be inside a SizedBox of fixed size?
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
List<Widget> graphWidgets = const [
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
];
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: SizedBox(
// This is the window the chart is displayed in
height: 200,
width: 400,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(),
),
child: OverflowBox(
// Used to prevent graph rows from wrapping in the window
maxHeight: double.infinity,
maxWidth: double.infinity,
alignment: Alignment.topLeft,
child: DecoratedBox(
// Debug widget to show extent of child column
decoration: BoxDecoration(
border: Border.all(),
color: Colors.amber,
),
child: Center(
// This should allow the Column to be smaller than the SizedBox?
child: Column(
// This holds the widgets that make up the graph
mainAxisSize: MainAxisSize.min,
children: graphWidgets,
),
),
),
),
),
),
),
),
);
}
}
As per SayyidJ's reply, the answer is to use a ConstrainedBox rather than a SizedBox. The Column can shrink to fit the contents when it is the child of a ConstrainedBox.
Here is the revised code, which you can run in DartPad, showing this working.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
List<Widget> graphWidgets = const [
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
Text(
'long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text-long-line-of-text'),
];
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ConstrainedBox(
// This is the window the chart is displayed in
constraints: BoxConstraints(
maxHeight: 200,
maxWidth: 400,
),
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(),
),
child: OverflowBox(
// Used to prevent graph rows from wrapping in the window
maxHeight: double.infinity,
maxWidth: double.infinity,
alignment: Alignment.topLeft,
child: DecoratedBox(
// Debug widget to show extent of child column
decoration: BoxDecoration(
border: Border.all(),
color: Colors.amber,
),
child: Column(
// This holds the widgets that make up the graph
mainAxisSize: MainAxisSize.min,
children: graphWidgets,
),
),
),
),
),
),
),
);
}
}

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

Drawing Widgets at specific pixel locations for different screen sizes

I'm trying to build a simple Flutter application that displays a full-screen background image and enables the user to drag certain widgets (i.e. a basic circle) from pre-defined start positions (given in pixels) to pre-defined target positions (also given in pixels). The following screenshot from the TouchSurgery app shows a very similar setup to what I'm trying to achieve (green circle = start position, white circle = target position):
My biggest concern at this point are different screen sizes. Let's assume we have an iPhone SE (second generation) with a resolution of 750 x 1334. I can create the following background image with the desired resolution and randomly determine the desired start position to be at coordinates (430, 949) (for simplicity we can disregard the target position):
With the following widget, I can render a circular Container on top of the starting point:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var dpr = MediaQuery.of(context).devicePixelRatio;
return Scaffold(
body: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/iPhoneSE.png"),
fit: BoxFit.fill,
),
),
),
Positioned(
left: 430 / dpr,
top: 949 / dpr,
child: Container(
width: 77.0 / dpr,
height: 77.0 / dpr,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
),
),
),
],
),
);
}
}
The resulting image looks like this:
Things start to get tricky when I add an AppBar or a BottomNavigationBar to my application. Both Widgets have a default height of 56 pixels. Given a devicePixelRatio of 2 on the iPhone SE, I need to crop the size of my background image to 750 x 1110 for the overlay to still be accurate (1334 - 2 * 56 (AppBar) - 2 * 56 (BottomNavigationBar)).
Things get even more complicated for other devices such as the iPhone XR, where also the size of the safe area has to be considered. And for Android, there's even more different screen resolutions available.
My question now is the following: instead of creating differently sized background images for 20-30+ different screen sizes - is there a more efficient way in Flutter to draw widgets such as a circular Container at very specific screen locations that works independently of the actual screen size?
You need to get the size of the image container BEFORE positioning your Positioned Widget.
Because as you said, the screen size could change, independently of the image size (e.g. The screen is taller but has a bigger SafeArea, or has an appBar and BottomAppBar. The image could be the same size even if the screen size increased...)
Since your Positioned widget and your image Container are in the same build method, you have to use a LayoutBuilder widget to track the size of your image Container before moving on to building your Positioned widget.
Here's how:
(I've included 2 fully working examples so that you can see that the red circle keeps the same relative position to the background image, even when the image size changes. Your corrected code is the first example).
Example 1
/*
I used these calculated ratios based on your code.
Feel free to use any other way to get these ratios.
The method will be the same.
- The image takes all the available width and height
- The positioned element is postioned :
58.9% from the left of the image container
72% from the top of the image container
- The inner container's:
width is 7.129629629% of the Image Container's width,
height is 4.292084726% of the Image Container's height,
*/
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) { //This is key
return Scaffold(
body: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/iPhoneSE.png"),
fit: BoxFit.fill,
),
),
),
Positioned(
left: 0.589 * constraints.maxWidth,
top: 0.72 * constraints.maxHeight,
child: Container(
width: 0.07129629629 * constraints.maxWidth,
height: 04292084726 * constraints.maxHeight,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
),
),
),
],
),
);
});
}
}
Example 1 image:
Example 2 (with an AppBar and BottomAppBar)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Title of app"),
),
body: LayoutBuilder(builder: (context, constraints) {
return Column(
children: <Widget>[
Flexible(
fit: FlexFit.loose,
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/iPhoneSE.png"),
fit: BoxFit.fill,
),
),
),
Positioned(
left: 0.589 * constraints.maxWidth,
top: 0.72 * constraints.maxHeight,
child: Container(
width: 0.07129629629 * constraints.maxWidth,
height: 0.04292084726 * constraints.maxHeight,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
),
),
),
],
),
),
],
);
}),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.home,
),
title: Text("Home")),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle), title: Text("Profile")),
],
),
);
}
}
Example 2 image:
How about using a Transform.scale widget on your stack and just resizing the whole thing to fit inside whatever constraints the device has?
Something like this:
Transform.scale(
alignment: Alignment.topLeft,
scale: scaleVar,
child: Stack(
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: Image(
image: AssetImage("assets/iPhoneSE.png"),
alignment: Alignment.topLeft,
),
),
Positioned(
left: 430,
top: 949,
child: Container(
width: 77.0,
height: 77.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
),
),
),
],
),
)

How to make relative layout in flutter app

Please see this image -https://ibb.co/YpjJsLZ
In above image how can I place burger image to center of white view.
I tried using Align & Center widget but its no fitting properly.
How can relative layout works in flutter?
Flutter does not have layout types like android,
But you can place burger image using stack widget.
You can follow this tutorial for reference.
you can not use Relative Layout in Flutter but it Replace using "Stack".
The stack is a widget in Flutter that contains a list of widgets and positions them on top of the other.
stack allows developers to overlap multiple widgets into a single screen and renders them from bottom to top.
Hence, the first widget is the bottommost item, and the last widget is the topmost item.
import 'package:flutter/material.dart';
class StackWidget extends StatefulWidget {
#override
_StackWidgetState createState() => _StackWidgetState();
}
class _StackWidgetState extends State<StackWidget> {
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: Colors.yellow,
height: 300,
child: Stack(
alignment: Alignment.topLeft,
children: [
Container(
color: Colors.cyan,
width: 200,
height: 200,
),
Container(
color: Colors.red,
width: 150,
height: 150,
),
Container(
color: Colors.blue,
width: 100,
height: 100,
),
],
),
);
}
}
Output:
Done☻♥.

Animating Widget to a position outside of it's parent (Row/Listview)

I have a list of card widgets inside a row or listview. When clicking on of these cards i want to create an effect where the card grows and moves to the middle of the screen, and the rest of the screen appears below a grey overlay.
These example images should help you understand what i'm trying to explain.
Image #1 - List before Clicking in any card
Image #2 - After clicking a card. The card animates in size and position to the center of the screen. Everything else gets becomes dark. (Ignore bad Photoshop).
I'm not asking for full code or anything, just want to know if it's possible to move a widget outside it's parent and get some ideas of how to achieve this effect. I know AnimatedContainer can be used on the card to make it grow, the positioning part is what need help with. Thanks!
You can use the transform: argument on a Container()
Full Working Example
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
color: Colors.black87,
margin: EdgeInsets.only(top: 100),
child: Row(
children: <Widget>[
Container(
margin: EdgeInsets.symmetric(horizontal: 16),
transform: Matrix4.translationValues(0, 50, 0),
color: Colors.red,
height: 100,
width: 100,
),
Container(
margin: EdgeInsets.symmetric(horizontal: 16),
color: Colors.red,
height: 100,
width: 100,
),
Container(
margin: EdgeInsets.symmetric(horizontal: 16),
color: Colors.red,
height: 100,
width: 100,
),
],
),
)
)
);
}
}
The #2 Animation can be accomplished by using a Hero() Widget and Overlay(). Or you can use a custom DialogBuilder just a few Suggestions to get you started.