How to lay out Rows and Columns in Flutter widget - flutter

I am trying to build the UI for a simple screen with a few Text and TextBoxes but I am getting weird layout errors like this.
Error: Cannot hit test a render box that has never been laid out.
The hitTest() method was called on this RenderBox: RenderStack#487f4 NEEDS-LAYOUT NEEDS-PAINT:
creator: Stack ← _FloatingActionButtonTransition ← MediaQuery ← LayoutId-[<_ScaffoldSlot.floatingActionButton>] ← CustomMultiChildLayout ← AnimatedBuilder ← DefaultTextStyle ← AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#0cc7b ink renderer] ← NotificationListener<LayoutChangedNotification> ← PhysicalModel ← AnimatedPhysicalModel ← ⋯
parentData: offset=Offset(0.0, 0.0); id=_ScaffoldSlot.floatingActionButton
constraints: MISSING
size: MISSING
alignment: Alignment.centerRight
textDirection: ltr
fit: loose
Unfortunately, this object's geometry is not known at this time, probably because it has never been laid out. This means it cannot be accurately hit-tested.
If you are trying to perform a hit test during the layout phase itself, make sure you only hit test nodes that have completed layout (e.g. the node's children, after their layout() method has been called).
at Object.throw_ [as throw] (http://localhost:61969/dart_sdk.js:5061:11)
at http://localhost:61969/packages/flutter/src/rendering/layer.dart.lib.js:4204:23
at stack.RenderStack.new.hitTest (http://localhost:61969/packages/flutter/src/rendering/layer.dart.lib.js:4209:26)
at http://localhost:61969/packages/flutter/src/rendering/layer.dart.lib.js:7665:44
at box.BoxHitTestResult.wrap.addWithPaintOffset (http://localhost:61969/packages/flutter/src/rendering/layer.dart.lib.js:7418:19)
...
So, I removed all code and kept only two text boxes and two text fields to understand what is causing the problem. But I still can't figure out the reason. Here is my simplified code for the build method of my stateless widget:
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text("Create"),
),
body: Container(
child:Column(
children: [
//First row - Amount and Ccy labels/text boxes
Expanded(
child: Row(
children: [
Expanded(
child:Column(
mainAxisSize: MainAxisSize.max,
children: [
Text('Amount'),
TextField(),
],
)
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Currency'),
TextField(),
],
)
],
)
),
//Second Row - Comment label/text box
]
)
)
);
}
I wonder if is some basic concept that I am missing here. I am testing it on Chrome.

If you use TextField inside SizedBox with the determined height it might works

After putting code i don't have this problem. is this UI the thing you wanted to achieve?
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "title",
theme: ThemeData(primarySwatch: Colors.green),
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: const Text("Create"),
),
body: Container(
child: Column(children: [
//First row - Amount and Ccy labels/text boxes
Row(children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
children: const [
Text('Amount'),
TextField(),
],
)),
Column(
mainAxisSize: MainAxisSize.min,
children: const [
Text('Currency'),
],
)
//Second Row - Comment label/text box
])
])),
));
}
}

I think I found the answer to the issue. When I put two textfields in a row, with the first one in an Expanded widget, flutter isn't able to compute the right width to assign for the second one because TextFields don't have any width of their own unlike Text.
So, its parent has to provide a width or flutter has to have something idea about what width to give it to that TextField. When I put both the textfields in Expanded of their own and assign a flex value to each, it works fine. Like this:
Expanded(
flex:3, //this column is thrice as wide as the second one
child: Column(
mainAxisSize: MainAxisSize.max,
children: const [
Text('Amount'),
TextField(),
],
)),
Expanded(
flex: 1,
child:Column(
mainAxisSize: MainAxisSize.min,
children: const [
Text('Currency'),
TextField(),
],
)
)
I hope this is it.

Related

Flutter Expanded widget content won't scroll

When my Expanded widget has expanded there are 20 items, which cause a vertical overflow issue. How do I get the contents within Expanded widget to scroll? I've tried wrapping the content in a SinglechildScrollView but it still didn't allow scrolling.
Scaffold(
key: key,
appBar: customappbar,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
ExpansionTile(
title: Text(
"${_getcurrentselection(context)}:",
style: const TextStyle(fontSize: 16),
),
children: <Widget>[
for (Map<String, dynamic> submenuitemdata in subMenuItems)
Padding(
padding: const EdgeInsets.only(bottom:10.0),
child: SubmenuItem(submenuitem: submenuitemdata),
),
Expanded(
child: WebViewWidget(controller: controllerGlobal),
),
],
),
],
),
),
Try putting your widgets inside a ListView which should be inside the Expanded.
I couldn't find a suitable solution sa the Expanded widget below the ExpansionTile widget kept throwing errors no matter what I tried. Instead I placed both inside a Stack, so that the ExpansionTile expands over the top of any other widgets.
Also, minus 2 was a bit harsh

Flutter error when wrapping a Positioned widget inside Stack and Column (A RenderFlex overflowed by Infinity pixels.)

I have a use case where I have to use Stack and Positioned to make some overlapping widgets. But I also have to wrap that inside a Column. When I do that then I get an error
A RenderFlex overflowed by Infinity pixels on the bottom.
A minimal example showing the issue:
import "package:flutter/material.dart";
class TestScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Example"),
),
body: Column(
children: [
Stack(children: [
cardWidget(),
]),
],
),
),
);
}
Widget cardWidget() {
return Positioned(
left: 10.0,
child: Card(
color: Colors.green,
child: Container(
height: 200,
width: 100,
),
),
);
}
}
If I remove left: 10 then it works fine but thats not what I want.
The full error message (which I don't really understand):
The following assertion was thrown while applying parent data.:
Incorrect use of ParentDataWidget.
The ParentDataWidget Positioned(left: 10.0) wants to apply ParentData of type StackParentData to a
RenderObject, which has been set up to accept ParentData of incompatible type FlexParentData.
Usually, this means that the Positioned widget has the wrong ancestor RenderObjectWidget. Typically,
Positioned widgets are placed directly inside Stack widgets.
The offending Positioned is currently placed inside a Column widget.
The ownership chain for the RenderObject that received the incompatible parent data was:
Semantics ← Card ← Positioned ← Column ← _BodyBuilder ← MediaQuery ←
LayoutId-[<_ScaffoldSlot.body>] ← CustomMultiChildLayout ← AnimatedBuilder ← DefaultTextStyle ← ⋯
This is from the Flutter docs, for the Column class.
One common reason for this to happen is that the Column has been placed in another Column (without using Expanded or Flexible around the inner nested Column). When a Column lays out its non-flex children (those that have neither Expanded or Flexible around them), it gives them unbounded constraints so that they can determine their own dimensions (passing unbounded constraints usually signals to the child that it should shrink-wrap its contents). The solution in this case is typically to just wrap the inner column in an Expanded to indicate that it should take the remaining space of the outer column, rather than being allowed to take any amount of room it desires.
This is enough to understand what the problem is
The Column is giving the Stack unbounded constraints, due to which you're getting overflow error.
Putting it inside an Expanded widget however, solves it as Expanded is a flex-widget and it tries to cover entire available space. It then gives constraints to the Stack which prevents it from overflow.
Hope I helped.
Turns out I need to wrap my Stack with Expanded. Not sure why.
import "package:flutter/material.dart";
class TestScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Example"),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Stack(
children: [0, 1, 2, 3].map((e) => cardWidget(e)).toList(),
),
),
],
),
),
);
}
Widget cardWidget(int index) {
return Positioned(
left: index * 10.0,
child: Card(
elevation: 10,
color: Colors.green,
child: Container(
height: 200,
width: 100,
),
),
);
}
}
The issue with this solution is that it won't center my cards (neither horizontal or vertical). How is that achieved?
You can add symmetric Padding to the container to center the Stack. Also, add alignment: AlignmentDirectional.center to the Stack to center the widgets inside it(they will only be vertically centered, since they are Positioned widgets with left parameter provided).
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 80.0),
child: Stack(
alignment: AlignmentDirectional.center,
children: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((e) => cardWidget(e)).toList(),
),
),
),
],
),
);
}
Widget cardWidget(int index) {
return Positioned(
left: index * 50.0,
child: Card(
elevation: 10,
color: Colors.green,
child: Container(
height: 200,
width: 100,
),
),
);
}
Note: adjust the padding amount according to the cards' size and the screen size.
Result:

How to make a Widget to be as small as it can (wrap_content in Android)?

I have a rendering issue with render tree similar to Column>PageView>Column, where the last Column is inside a page of the PageView.
The PageView isn't being rendered correctly, so I get an exception (Horizontal viewport was given unbounded height.) as the framework can't calculate its sizes, I can fix it by wrapping it into a Flexible or an Expanded, but I don't want the PageView to take the whole screen, I want it to be as small as possible and on the center on the screen.
Here's a representation of my problem:
// This code throws an exception:
class Widget_1_Has_Problem extends StatelessWidget {
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.done,size: 112),
SizedBox(height: 32),
PageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
// Many widgets go here, I am just simplifying with a Column.
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
],
),
],
),
);
}
}
This is what I wanted to achieve (I removed the PageView in order to show it):
class Widget_2_No_PageView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.done,size: 112),
SizedBox(height: 32),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
],
),
);
}
}
And this is how I can fix it, but it's not perfect, I will show later:
class Widget_3_With_Flex_Not_Perfect_Solution extends StatelessWidget {
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(flex: 2,),
Icon(Icons.done,size: 112),
SizedBox(height: 32),
Flexible(
flex: 1,
child: PageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
],
),
),
Spacer(flex: 2,),
],
),
);
}
}
This solution isn't perfect because Spacer and Flexible or Expanded always try to maintain it's proportionality of the flex weight, which means that for smaller displays the Spacer won't vanish, it will remain there, whereas the first and desired image won't have such a void space. As we can see in the below image, the Spacer is always there. Also I have to calculate the flex factor for every change in this design, whereas in my first code example the widget will size itself to the center of the screen automatically.
How can I instruct PageView then to be as small as it can be, instead of expand as much as it wants, as that's the only solution I can find online?
The PageView uses SliverFillViewport to wrap around given children.
This SliverFillViewport doesn't care about how small the children are. It only cares how much space it can occupy from its parent. So the Flex and Align widget are useless.
Currently with PageView, it most likely not possible unless we do some calculation and use SizedBox or ConstrainedBox.
I also found this discussion
This can be done, but not with PageView. I had to use SingleChildScrollView with a Row. The reason for that is: if you want to size your PageView properly, you need to build and layout every child it has. PageView does not do that, but Row does, you can even decide how smaller elements should be positioned. PageController also works if you properly set widths of children in the row.
Note that this will only work nicely if you have small amount of pages.
class SizingPageView extends StatelessWidget {
final ScrollPhysics physics;
final PageController controller;
final List<Widget> children;
const SizingPageView({Key key, this.physics, this.controller, this.children})
: super(key: key);
#override
Widget build(BuildContext context) {
Widget transform(Widget input) {
return SizedBox(
width: MediaQuery.of(context).size.width,
child: input,
);
}
return SingleChildScrollView(
physics: physics ?? PageScrollPhysics(),
controller: controller,
scrollDirection: Axis.horizontal,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: children.map(transform).toList(),
),
);
}
}
class Solution extends StatelessWidget {
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(
flex: 2,
),
Icon(Icons.done, size: 112),
SizedBox(height: 32),
Text('BEFORE SCROLL'),
SizingPageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
],
),
Text('Third'),
],
),
Text('AFTER SCROLL'),
Spacer(
flex: 2,
),
FlatButton(
child: Text('Next'),
onPressed: () {
var next = pageController.page.round() + 1;
if (next >= 3) {
next = 0;
}
pageController.animateToPage(
next,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
);
},
)
],
),
);
}
}
You are asking for items inside of the PageView to set the size of PageView itself. This is not going to work with the standard class. If you look at its source, you'll find that uses Viewport inside Scrollable. Viewport grabs maximum size and ignores the size of the children (Slivers). You'll need to use a different approach or maybe create a custom PageView, perhaps one that uses ShrinkWrappingViewport.
https://api.flutter.dev/flutter/widgets/ShrinkWrappingViewport-class.html
By wrapping PageView inside SizedBox, we can get rid of the exception you are getting. (Horizontal viewport was given unbounded height.).
SizedBox(height: 32, child:
PageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
// Many widgets go here, I am just simplifying with a Column.
Column(
...
But that will give you a bottom overflow exception of 16 pixels since you have another SizedBox with fixed height inside Column in PageView.
In order to resolve this issue, let's just for time being replace top SizedBox with Container and add color property to it to see how much space and height is available to us.
So, instead of hardcoding the sizedbox height to 32, we can make use of MediaQuery to calculate height for us.
If you print height of the top SizedBox, it will come out to be 597
SizedBox(height: MediaQuery.of(context).size.height,
I for demo, divided the height with 5 to get 119.4 height of the SizedBox widget that contains PageView, which resolves the renderflow exception as well as maintains pageview's location at center of the screen.
If we again use Container to see now how much space and height is available to us, it will look like:
So with this approach, you will not be required to use Expanded or Flexible widgets as you mentioned and won't need to change much of your code.
The working code is as below:
h = MediaQuery.of(context).size.height / 5;
print(h); // 119.4
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.done,size: 112),
SizedBox(height: h, child:
PageView(
physics: NeverScrollableScrollPhysics(),
controller: pageController,
children: <Widget>[
// Many widgets go here, I am just simplifying with a Column.
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
SizedBox(height: 16),
Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
],
),
],
),
),
],
),
);
You might need to adjust height per your need, but I hope this resolves your issue and answers your question.
You can simply wrap your widget with Flexible widget https://api.flutter.dev/flutter/widgets/Flexible-class.html

How to create a horizontally scrolling table with fixed column in Flutter?

I would like to create a series of tables that you can scroll through vertically, each of which may have a different number of rows/columns from each other.
Within each table, I would like to have the leftmost column frozen in place, and the remaining columns in that table to be horizontally scrollable, in case there are a number of columns that do not fit in the width of the screen. See screenshot:
My initial plan was to use a ListView for the page-level vertical scrolling between tables, and within each table, there is a Row of Columns, where the first column is a static width, and the remaining columns are enclosed within a horizontally scrolling ListView. The error I'm getting from Flutter is not helping me determine what I need to do, but it clearly has to do with having to set bounds on child Widgets.
Error: (Fixed 7/9/19 by wrapping horizontal ListView with a fixed height container and shrinkwrapping the ListView)
The following assertion was thrown during performResize():
Horizontal viewport was given unbounded width.
Viewports expand in the scrolling direction to fill their container.In this case, a horizontal
viewport was given an unlimited amount of horizontal space in which to expand. This situation
typically happens when a scrollable widget is nested inside another scrollable widget.
If this widget is always nested in a scrollable widget there is no need to use a viewport because
there will always be enough horizontal space for the children. In this case, consider using a Row
instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
the width of the viewport to the sum of the widths of its children.
New Error 7/9/19:
The following message was thrown during layout:
A RenderFlex overflowed by 74 pixels on the right.
The overflowing RenderFlex has an orientation of Axis.horizontal.
The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and
black striped pattern. This is usually caused by the contents being too big for the RenderFlex.
Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the
RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be
seen. If the content is legitimately bigger than the available space, consider clipping it with a
ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex,
like a ListView.
The specific RenderFlex in question is:
RenderFlex#9bf67 relayoutBoundary=up5 OVERFLOWING
creator: Row ← RepaintBoundary-[<0>] ← IndexedSemantics ←
NotificationListener ← KeepAlive ← AutomaticKeepAlive ← SliverList ←
SliverPadding ← Viewport ← IgnorePointer-[GlobalKey#74513] ← Semantics ← Listener ← ⋯
parentData: (can use size)
constraints: BoxConstraints(w=404.0, 0.0<=h<=Infinity)
size: Size(404.0, 300.0)
direction: horizontal
mainAxisAlignment: start
mainAxisSize: max
crossAxisAlignment: center
textDirection: ltr
This was the issue I ran into originally before getting side-tracked with the first issue reported; I can't understand why my ListView is not creating a scrollable container.
Code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: Scaffold(
appBar: AppBar(
title: Text('My App'),
backgroundColor: Colors.teal[400],
),
body: MyClass(),
),
);
}
}
const double headerCellWidth = 108.0;
const double cellPadding = 8.0;
const double focusedColumnWidth = 185.0;
const double rowHeight = 36.0;
class MyClass extends StatefulWidget {
#override
_MyClassState createState() => _MyClassState();
}
class _MyClassState extends State<MyClass> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.all(5.0),
children: <Widget>[
Row(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
color: Colors.grey,
padding: EdgeInsets.all(cellPadding),
width: headerCellWidth,
),
HeaderCell('ABC'),
HeaderCell('123'),
HeaderCell('XYZ'),
],
),
Container(
height: 300.0, // Could compute height with fixed rows and known number of rows in advance
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: <Widget>[
Column(
children: <Widget>[
Container(
color: Colors.grey[300],
padding: EdgeInsets.all(cellPadding),
height: rowHeight,
width: focusedColumnWidth,
),
NumberCell('89'),
NumberCell('92'),
NumberCell('91'),
NumberCell('90'),
NumberCell('91'),
NumberCell('89'),
],
),
Column(
children: <Widget>[
Container(
color: Colors.grey[300],
padding: EdgeInsets.all(cellPadding),
height: rowHeight,
width: focusedColumnWidth,
),
NumberCell('89'),
NumberCell('92'),
NumberCell('91'),
NumberCell('90'),
NumberCell('91'),
NumberCell('89'),
],
),
],
),
),
],
),
],
);
}
}
class HeaderCell extends StatelessWidget {
HeaderCell(this.text);
final String text;
#override
Widget build(BuildContext context) {
return Container(
height: rowHeight,
padding: EdgeInsets.all(cellPadding),
width: headerCellWidth,
child: Text(
text,
textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
);
}
}
class NumberCell extends StatelessWidget {
NumberCell(this.text);
final String text;
#override
Widget build(BuildContext context) {
return Container(
height: rowHeight,
width: focusedColumnWidth,
padding: EdgeInsets.all(cellPadding),
child: Text(
text,
),
);
}
}
Here is a quick example and this would be the result: Video
List<Widget> _buildCells(int count) {
return List.generate(
count,
(index) => Container(
alignment: Alignment.center,
width: 120.0,
height: 60.0,
color: Colors.white,
margin: EdgeInsets.all(4.0),
child: Text("${index + 1}", style: Theme.of(context).textTheme.title),
),
);
}
List<Widget> _buildRows(int count) {
return List.generate(
count,
(index) => Row(
children: _buildCells(10),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SingleChildScrollView(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildCells(20),
),
Flexible(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildRows(20),
),
),
)
],
),
),
);
}
So I tried to produce a minimum working bit of code, and ended up with a workable solution (even if all the details aren't ironed out, like the first locked column being of flexible width instead of a fixed width as desired). Hopefully this will help others trying to produce something similar. What's interesting is that the Table construct is needed here, because replacing the TableRow (wrapped by Table) with just a Row causes an overflow error. I would still be interested in understanding why that is since it seems crucial to the layout engine.
#override
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.all(5.0),
children: <Widget>[
Table(
children: <TableRow>[
TableRow(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// first locked column items
],
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
// table header items
],
),
Row(
children: <Widget>[
// data cells
],
),
Row(
children: <Widget>[
// data cells
],
),
],
),
),
],
),
],
),
],
);
}
If there is not much customization needed, for those needed a fixed header and first column table may also consider to use the horizontal_data_table package:
https://pub.dev/packages/horizontal_data_table
It basically is using the two list view approach.

How to use Expanded in SingleChildScrollView?

How to use Expanded in SingleChildScrollView? I have a screen with Image.network, ListView.builder and Row (TextFormField and IconButton). I wrapped ListView with Expanded. How to wrap this column with SingleChildScrollView? I need to move screen when the keyboard is open to see what I am writing. When I wrap my column I have this error.
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
child: GestureDetector(
child:
Image.network(
postOne.imageUrl,
fit: BoxFit.fitWidth,
height: MediaQuery
.of(context)
.size
.width,
width: MediaQuery
.of(context)
.size
.width,
),
onLongPress: () {},
onDoubleTap: () {},
),
),
Expanded(
//height: MediaQuery.of(context).size.width*0.33,
child: ListView.builder(
itemCount: commentList.length,
itemBuilder: (context, position) {
return GestureDetector(
onLongPress: () {},
child: Card(
child: Padding(
padding: EdgeInsets.all(5.0),
child: new CheckboxListTile(
title: new Text(commentList
.elementAt(position)
.coment,
style: TextStyle(fontSize: 18.0),),
value: values[commentList
.elementAt(position)
.coment],
onChanged: (bool value) {}),
),
)
);
}
),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
new Flexible(
child: Theme(
data: new ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.grey,
inputDecorationTheme: new InputDecorationTheme(
labelStyle: new TextStyle(
color: Colors.black45, fontSize: 18.0
),
)
),
child: new Form(
key: _formKey,
child: new TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Please enter the comment';
}
},
controller: commentController,
decoration: new InputDecoration(
labelText: "Add comment",
//hintText: 'Add comment'
),
keyboardType: TextInputType.text,
),
),
),
),
new Container(
margin: EdgeInsets.only(left: 10.0, top: 12.0),
child: new IconButton(
icon: new Icon(Icons.send, color: Colors.black,),
onPressed: () {}
)
),
]),
),
],
),
),
I/flutter ( 6816): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 6816): The following assertion was thrown during performLayout():
I/flutter ( 6816): RenderFlex children have non-zero flex but incoming height constraints are unbounded.
I/flutter ( 6816): When a column is in a parent that does not provide a finite height constraint, for example if it is
I/flutter ( 6816): in a vertical scrollable, it will try to shrink-wrap its children along the vertical axis. Setting a
I/flutter ( 6816): flex on a child (e.g. using Expanded) indicates that the child is to expand to fill the remaining
I/flutter ( 6816): space in the vertical direction.
I/flutter ( 6816): These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child
I/flutter ( 6816): cannot simultaneously expand to fit its parent.
I/flutter ( 6816): Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible
I/flutter ( 6816): children (using Flexible rather than Expanded). This will allow the flexible children to size
I/flutter ( 6816): themselves to less than the infinite remaining space they would otherwise be forced to take, and
I/flutter ( 6816): then will cause the RenderFlex to shrink-wrap the children rather than expanding to fit the maximum
I/flutter ( 6816): constraints provided by the parent.
I/flutter ( 6816): The affected RenderFlex is:
I/flutter ( 6816): RenderFlex#9f534 relayoutBoundary=up11 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 6816): The creator information is set to:
I/flutter ( 6816): Column ← _SingleChildViewport ← IgnorePointer-[GlobalKey#3670d] ← Semantics ← Listener ←
I/flutter ( 6816): _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#4878e] ←
I/flutter ( 6816): Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#c5885] ← RepaintBoundary ← CustomPaint ←
I/flutter ( 6816): ⋯
I/flutter ( 6816): The nearest ancestor providing an unbounded width constraint is:
I/flutter ( 6816): _RenderSingleChildViewport#155d8 relayoutBoundary=up10 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 6816): creator: _SingleChildViewport ← IgnorePointer-[GlobalKey#3670d] ← Semantics ← Listener ←
I/flutter ( 6816): _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#4878e] ←
I/flutter ( 6816): Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#c5885] ← RepaintBoundary ← CustomPaint ←
I/flutter ( 6816): RepaintBoundary ← ⋯
I/flutter ( 6816): parentData: <none> (can use size)
I/flutter ( 6816): constraints: BoxConstraints(0.0<=w<=440.8, 0.0<=h<=649.3)
I/flutter ( 6816): size: MISSING
I/flutter ( 6816): See also: https://flutter.dev/layout/
I/flutter ( 6816): If this message did not help you determine the problem, consider using debugDumpRenderTree():
I/flutter ( 6816): https://flutter.dev/debugging/#rendering-layer
I/flutter ( 6816): http://docs.flutter.io/flutter/rendering/debugDumpRenderTree.html
I/flutter ( 6816): If none of the above helps enough to fix this problem, please don't hesitate to file a bug:
I/flutter ( 6816): https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 6816):
Instead of using SingleChildScrollView, It's easier to use CustomScrollView with a SliverFillRemaining.
Try this:
CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
children: <Widget>[
const Text('Header'),
Expanded(child: Container(color: Colors.red)),
const Text('Footer'),
],
),
),
],
)
Try this,
LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
Text("Header"),
Expanded(
child: Container(
color: Colors.red,
),
),
Text("Footer"),
],
),
),
),
);
},
)
I got this solution from git issues when I get into the same situation. I don't have the git link. I think it may help you.
Reusable widget:
Note: use it, only if one of the children is Expanded
import 'package:flutter/material.dart';
class ScrollColumnExpandable extends StatelessWidget {
final List<Widget> children;
final CrossAxisAlignment crossAxisAlignment;
final MainAxisAlignment mainAxisAlignment;
final VerticalDirection verticalDirection;
final TextDirection textDirection;
final TextBaseline textBaseline;
final EdgeInsetsGeometry padding;
const ScrollColumnExpandable({
Key key,
this.children,
CrossAxisAlignment crossAxisAlignment,
MainAxisAlignment mainAxisAlignment,
VerticalDirection verticalDirection,
EdgeInsetsGeometry padding,
this.textDirection,
this.textBaseline,
}) : crossAxisAlignment = crossAxisAlignment ?? CrossAxisAlignment.center,
mainAxisAlignment = mainAxisAlignment ?? MainAxisAlignment.start,
verticalDirection = verticalDirection ?? VerticalDirection.down,
padding = padding ?? EdgeInsets.zero,
super(key: key);
#override
Widget build(BuildContext context) {
final children = <Widget>[const SizedBox(width: double.infinity)];
if (this.children != null) children.addAll(this.children);
return LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: Padding(
padding: padding,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - padding.vertical,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: crossAxisAlignment,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: MainAxisSize.max,
verticalDirection: verticalDirection,
children: children,
textBaseline: textBaseline,
textDirection: textDirection,
),
),
),
),
);
},
);
}
}
The answer is in the error itself. When the column is inside a view that is scrollable, the column is trying to shrink-wrap its content but since you used Expanded as a child of the column it is working opposite to the column trying to shrink-wrap its children. This is causing this error because these two directives are completely opposite to each other.
As mentioned in the error logs try the following:
Consider setting mainAxisSize to MainAxisSize.min (for column) and using FlexFit.loose fits for the flexible(use Flexible rather than Expanded).
I tried Vijaya Ragavan solution but did some adjustments to it & it still works.
To use Expanded with SingleChildScrollView, I used ConstrainedBox and set its height to the height of the screen (using MediaQuery). You'll just need to make sure the screen content you put inside ConstrainedBox is not bigger than the height of the screen.
Otherwise set the height of ConstrainedBox to height of the content you want to display on the screen.
SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Text('Hello World!'),
),
],
),
)
)
Edit:
To subtract the height of the AppBar and/or the Status Bar, see below:
double screenHeightMinusAppBarMinusStatusBar = MediaQuery.of(context).size.height
- appBar.preferredSize.height
- MediaQuery.of(context).padding.top;
Simply wrap your SingleChildScrollView in a Center or an Align element.
Example :
Align(
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
...
]
}
}
}
or
Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
...
]
}
}
}
You can simply wrap the column in a sized box and give it a width and height as shown:
SingleChildScrollView(
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.9,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container() //widget here
const Expanded(
child: SizedBox(),
),
Container() //widget here
],
),
As already pointed out, because you are using a scrollable, you can't expand to the infinity (theoretically speaking), that's what's happening when you try to expand your ListView that is nested in a SingleChildScrollView.
You can try using a NestedScrollView, or, if it fits your demands and because you have commented out this line:
//height: MediaQuery.of(context).size.width*0.33,
You can just wrap your ListView in a ConstrainedBox (or even just a regular Container) with that height, for example, instead of the Expanded, like so:
Container(
height: MediaQuery.of(context).size.width*0.33,
child: ListView.builder(
itemCount: commentList.length,
...
)
)
Since you are already in a scrollable, you shouldn't have issues with smaller screens, because the whole tree is scrollable.
The trick is to only apply the ScrollView when you need to, and otherwise to let the content expand.
Something like this works well:
class ConstrainedFlexView extends StatelessWidget {
final Widget child;
final double minSize;
final Axis axis;
const ConstrainedFlexView(this.minSize, {Key key, this.child, this.axis}) : super(key: key);
bool get isHz => axis == Axis.horizontal;
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (_, constraints) {
double viewSize = isHz ? constraints.maxWidth : constraints.maxHeight;
if (viewSize > minSize) return child;
return SingleChildScrollView(
scrollDirection: axis ?? Axis.vertical,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isHz ? double.infinity : minSize,
maxWidth: isHz ? minSize : double.infinity),
child: child,
),
);
},
);
}
}
Usage:
ConstrainedFlexView(600, child: FlexContent())
This will flex to fill all vertical space, but once the widget is <600px it will switch to a constrained box + scroll view, allowing the content not to be squished too much.
Most of the answers are not taken into account wich you have a textfield widget, so when the keyboard open you will get a problem with the size of your content (it will be heigher than the screen), so you should to wrap one of the widgets inside the (expanded) at least with (flexible).
Scaffold(
resizeToAvoidBottomInset: true,
body:CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
children: <Widget>[
const TextField(),
Expanded(
child: Column(
children: [
Flexible(child: someWidget()),
]
)
),
],
),
),
],
)
)
If what you want is:
Being able to use expanded inside the SingleChildScrollView to fill the remaining screen.
Not being bothered by the keyboard either hidding the TextFormField you are writing into either resizing the content of the SingleChildScrollView.
I had the same problem.
Here is a maybe hazardous but in my case working solution I used:
import 'package:flutter/material.dart';
class FiniteSizeSingleChildScrollViewNotBotheredByKeyboard
extends StatefulWidget {
final Widget child;
const FiniteSizeSingleChildScrollViewNotBotheredByKeyboard(
{Key? key, required this.child})
: super(key: key);
#override
State<FiniteSizeSingleChildScrollViewNotBotheredByKeyboard> createState() =>
_FiniteSizeSingleChildScrollViewNotBotheredByKeyboardState();
}
class _FiniteSizeSingleChildScrollViewNotBotheredByKeyboardState
extends State<FiniteSizeSingleChildScrollViewNotBotheredByKeyboard> {
double width = 0, height = 0;
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
if (width != constraints.maxWidth) {
width = constraints.maxWidth;
height = constraints.maxHeight;
}
return SingleChildScrollView(
child: SizedBox(
width: width,
height: height,
child: widget.child,
),
);
});
}
}
The idea is to get the available size just before the SingleChildScrollView, and then to inject this size into a SizedBox which is inside the SingleChildScrollView. Also, to avoid the keyboard changing this size, there is a if condition which prevents changing the height if the width has not changed.
The only issue I uncontered yet with this custom widget, is that if a TextFormField controller inside this widget (lets call it widget A) call setState on a widget B containing this widget A which itself is a child of the keyed Form associated with the TextFormField, The contoller will trigger a rebuild at the same time as the keyboard will trigger a rebuild of the widget A, which generate an exception. To avoid this put the keyed Form inside the widget A (and not above).
I ran into the problem that a widget within the sub tree of the SliverFillRemaining / IntrinsicHeight was using a LayoutBuilder. And LayoutBuilder cannot be used in any widget tree that calculates its intrinsic dimensions (You will get an error saying that LayoutBuilder does not support returning intrinsic dimensions).
Since SliverFillRemaining with hasScrollBody: false also calculates the intrinsic dimensions of its child, it cannot be combined with any descendant widget that uses LayoutBuilder.
It is therefore not possible to combine both options in the same widget sub tree.
If your layout, however, does not use LayoutBuilder as a descendant of the SliverFillRemaining / IntrinsicHeight widget, but somewhere else in the scroll view, you can simply put it in a different sliver. Example reusing tanghao's code could look like this:
CustomScrollView(
slivers: [
// Use SliverList or any different sliver to display the children that use LayoutBuilder
SliverList(
delegate: SliverChildListDelegate(childrenContainingLayoutBuilder),
),
// Use the SliverFillRemaining for the sub tree that uses Expanded / Flexible / Spacer etc.
SliverFillRemaining(
hasScrollBody: false,
child: Column(
children: <Widget>[
const Text('Header'),
Expanded(child: Container(color: Colors.red)),
const Text('Footer'),
],
),
),
],
)