How to control size of a Column inside a SizedBox - flutter

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

Related

can't figure out where pixels are overlflow even thought I use MediaQuery height

As a demo, I have taken two container inside Column and I have used MediaQuery height for both container and deducting size of appear..eventhought it is showing 24 pixel overflow...and if I wrap column with SingleChildScrollView..it scrolls which should be not scrolled as both container's height sum is 1.
here is my demo code
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appbar=AppBar();
return Scaffold(
appBar: appbar,
body: SingleChildScrollView(
child: Column(
children: [
Container(
height: (MediaQuery.of(context).size.height-appbar.preferredSize.height)*0.40 ,
color: Colors.green,
),
Container(
height: (MediaQuery.of(context).size.height-appbar.preferredSize.height)*0.60 ,
color: Colors.red,
),
],
),
)
);
}
}
Column has property 'mainAxisSize', setting that to MainAxisSize.min will solve the problem.
Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: (MediaQuery.of(context).size.height-appbar.preferredSize.height)*0.40 ,
color: Colors.green,
),
In this case Column just stretches to infinity as ScrollView above allows it.
I got my answer from stack overflows history ....
here what I missed to deduct status bar height
height: (MediaQuery.of(context).size.height-appbar.preferredSize.height-MediaQuery.of(context).viewPadding.top)*0.60 ,

Flutter : How to color Sizedbox (without using container)

1. Explanation
I want to change full color of 'SizedBox' to see the location and scope of 'SizedBox'. I wonder is there any way to change SizedBox's color without filling it with Container or Decoration box. If not, I want to know how to fill SizedBox with decoration box.
2. Code
Here is a code that I tried.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home : Scaffold(
appBar: AppBar(
title : const Text('this is title'),
),
body : const SizedBox(
width : 200,
child: DecoratedBox(
decoration: BoxDecoration(
color : Colors.red,
),
),
),
bottomNavigationBar: (
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
Icon(Icons.favorite),
Icon(Icons.home),
Icon(Icons.settings)
],
)
),
),
);
}
}
**3. Result **
Here is what I got from the code.
You can wrap your SizedBox with ColoredBox
ColoredBox(
color: Colors.cyanAccent,
child: SizedBox(
width: 200,
height: 100,),
),
You can try ColoredBox widget
SizedBox(
width : 200,
height: 20,
child: ColoredBox(color: Colors.amber),
),
If you'd like to decorate the SizedBox to see the location and scope of the Widget just for debugging purposes, we can enable the debugPaintSizeEnabled just by pressing p on the CLI upon launching the flutter run command.
Otherwise, using a Container with explicit size parameters it's mostly the same as using SizedBox, and it would allow you to use have a background color or border decoration.
Another option would be to use a ColoredBox as the child of the SizedBox, or vice versa.
I'm not sure why you'd use it but you could have the child of the SizedBox be a container with the color you want. The container will stretch to the sizes provided
SizedBox(
height: 10,
width: 30,
child: Container(color: Colors.red),
),
Also you could use the widget inspector provided by flutter:
https://docs.flutter.dev/development/tools/devtools/inspector#:~:text=The%20Flutter%20widget%20inspector%20is,%2C%20rows%2C%20and%20columns).

How to fix a widget to the right side of a ListView on Flutter Web

I have a Flutter Web application where I need to show a widget on the right side of a ListView when I click an item and this widget should always be visible on screen. I can achieve my objective puting both on a Row and using a scrollable only for the ListView, but that requires the ListView to be wrapped by a widget with defined height.
Defining a container with height to wrap the ListView breaks the responsiveness when I resize the browser, as the container doesn't fit the height of the screen.
I thought of using the shrinkWrap property of the ListView so I don't have to wrap it in a widget with predefined height, but that makes the whole Row scrollable vertically, eventually causing the widget to leave the viewport.
I would appreciate if somebody knows how could I keep this right side widget fixed on screen so I can achieve my objective without losing responsiveness.
Here's something similitar to what I've got so far:
class PageLayout extends StatefulWidget {
const PageLayout({Key? key, required this.items}) : super(key: key);
final List<String> items;
#override
State<PageLayout> createState() => _PageLayoutState();
}
class _PageLayoutState extends State<PageLayout> {
final rightSideWidget = Container(
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(color: Colors.white, width: 2),
),
height: 200);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.49,
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) => Container(
decoration: BoxDecoration(
color: Colors.blue,
border: Border.all(color: Colors.white, width: 2),
),
height: 200,
child: Center(
child: Text(
widget.items[index],
style: const TextStyle(color: Colors.white),
),
),
),
itemCount: widget.items.length,
),
),
Expanded(child: rightSideWidget),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
I want rightSideWidget to be always centered on screen or follow the scroll.
You can divide your screen into two sections, right section and left section; thereby being able to control behaviour of widgets in both sections.
Divide the overall screen into 2 proportional sections using a Row
widget
Put this Row widget inside a Container with height equal to screen height for preserving responsiveness | Use MediaQuery to get current height of page
Now left hand section can individually scroll, and on click of any option from this section you can define behaviour for right section; while keeping the left section constant throughout page lifecycle

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

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.