Solvedflutter Wrapping a Column with an expanded in a SingleChildScrollView throws an exception
✔️Accepted Answer
It can be done this way:
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AppBar'),
),
body: LayoutBuilder(builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: constraints.maxWidth, minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Text('header'),
Expanded(
child: Container(
color: Colors.green,
child: Text('body'),
),
),
Text('footer'),
]
),
)
)
);
})
);
}
}
Other Answers:
One workaround I tries was to have an Expanded
widget with an empty container. Then whatever you want to add to bottom, add it after the expanded container.
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: new Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Text(
'Line 1',
),
new Text(
'Line 2',
),
new TextField(),
Expanded(child: new Container(),),
Column(
children: <Widget>[
new TextField(),
new Text(
'Should be at the bottom',
),
],
)
],
),
);
}
Does this help?
You must make Container height equals to double.maxInfinite
child: Container( height: double.maxFinite, child: .....
Working fine for me in master branch 2.1.0-30.
fwiw, here's a re-usable null safe version of this:
class ScrollableColumn extends StatelessWidget {
const ScrollableColumn(
{Key? key,
required this.children,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.textDirection,
this.mainAxisAlignment = MainAxisAlignment.start,
this.mainAxisSize = MainAxisSize.max,
this.verticalDirection = VerticalDirection.down,
this.textBaseline})
: super(key: key);
final List<Widget> children;
final CrossAxisAlignment crossAxisAlignment;
final TextDirection? textDirection;
final MainAxisAlignment mainAxisAlignment;
final MainAxisSize mainAxisSize;
final VerticalDirection verticalDirection;
final TextBaseline? textBaseline;
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
// Constrained box stops the column from getting too small, forcing it to be at least as tall as it's parent
constraints: BoxConstraints(minWidth: constraints.maxWidth, minHeight: constraints.maxHeight),
// Intrinsic height stops the column from expanding forever when it's height becomes unbounded
// It will always use the full height of the parent, or the natural size of the children, whichever is greater.
child: IntrinsicHeight(
child: Column(
children: children,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
),
),
),
);
});
}
}
Usage:
Widget build(BuildContext context){
Widget _box(String name) =>
Container(color: RandomColor().randomColor(), child: Text(name, style: TextStyle(fontSize: 128)));
return ScrollableColumn(
children: [
Expanded(child: _box("Content1")),
Expanded(child: _box("Content2")),
Expanded(child: _box("Content3")),
],
)
}
Scz2u5yuet.mp4
The recent refactor of SliverFillRemaining
(#35810) should be able to provide a solution here as well.
I've re-worked the workaround above to illustrate:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AppBar'),
),
body: SafeArea(
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: TextField(),
),
SliverFillRemaining(
hasScrollBody: false,
child: IntrinsicHeight(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Container(
color: Colors.green,
child: Text('body'),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Bottom'),
onPressed: () {},
),
),
],
),
),
),
],
),
),
);
}
}
There is another use case in #38641 that shows another SliverFillRemaining
layout.
Issues #38640 and #35753 refer to improving documentation around this widget and others that use flexible spacing/sizing and scrolling views.
Hi,
lets take this simple App, it should have a Textfield and a Text always at the bottom of the screen:
Which is a pretty common use case. Unfortunately the Textfield at the bottom won't get scrolled into view when getting focus so I wrap the Scaffold in a
SingleChildScrollView
which leads to this exception:Moving the
SingleChildScrollView
inside the Scaffold's body doesn't work either:I know that there is a workaround explained here https://docs.flutter.io/flutter/widgets/SingleChildScrollView-class.html but for such a standard requirement of using
Expanded
inside a Column that must be able to scroll up when the keyboard appears there should be a better solution. @Hixie wrote that Scaffold would take care of this but obviously doesn't.Please see also #18558 which shows another limitation of the workaround.
The sample app is here https://github.com/escamoteur/column_in_scrollable_repro