Reputation: 17756
I would like to create a view that has to have a Column
with a scroll view (e.g. something like SingleChildScrollView
) and a footer regardless of the screen size. If the screen is big enough, it will use the empty space between the scroll and the footer, if not, it will expand and only make the widget above the footer scrollable.
It's more or less like Listview with scrolling Footer at the bottom but with a diference that I want the keyboard to overflow the footer and it also should stay in place.
Something like
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(left: 30.0, right: 30.0, top: 80.0),
child: Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
// Multiple widgets and form fields
],
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 50.0),
child: SafeArea(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
// Footer widgets
],
),
),
)
],
),
);
Upvotes: 22
Views: 27182
Reputation: 1
Tricky, but sticky.
The only way I found in a few hours.
P.S. I need a header card, a message list, and a submit message footer, so SingleChildScrollView doesn't seem to be the best.
return CustomScrollView(
controller: controller,
slivers: [
YourAwesomeContent(),
SliverLayoutBuilder(
builder: (context, constraints) {
final minHeight = constraints.remainingPaintExtent - controller.offset;
return SliverToBoxAdapter(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: minHeight > 0 ? minHeight : 0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [YourAwesomeFooter()],
),
),
);
},
),
],
);
Upvotes: 0
Reputation: 113
To create a footer with scrollable screen we can use stack widget with ListView.builder and SingleChildScrollview as:
Scaffold(
body:Stack(
alignment: Alignment.bottomCenter,
children: [
ListView.builder(
itemCount: 1,
itemBuilder: (context, index) => SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container( // Scrollable contents here
color: Colors.red,
height: 3000,
width: 1000,
),
),
),
Container( // Footer
height:50,
color:Colors.blue[900],
width:MediaQuery.of(context).size.width,
child:Center(child:Text("Footer")),
),
],
),
);
Upvotes: 1
Reputation: 169
The accepted solution works in many cases, but it becomes tricky when you want to use something like a ListView because it can't provide an intrinsic height. I tried to find some different solution, and turns out I could, and it seems more flexible. I managed to solve this situation using slivers. Where the content is inside a sliver, and the footer is also inside a sliver.
Tip: Watch "The Boring Flutter Development Show, Ep. 12", which is all about slivers.
return Scaffold(
body: CustomScrollView(
shrinkWrap: true,
slivers: [
SliverToBoxAdapter(
child: Column(
children: [
//content widgets
],
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
//footer widgets,
],
),
),
],
),
);
Upvotes: 11
Reputation: 4022
For those who were looking to implement just footer with scrollview in a simple way, below code might help :
Scaffold(
appBar: buildAppBar('Some cool appbar'),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
PackageCard(),
PackageCard(),
PackageCard(),
],
),
),
),
Container(
child: Text('Your super cool Footer'),
color: Colors.amber,
)
],
),
);
Visual representation:-
---Column
|
|---Expanded--
|-SingleChildScrollView (column /YOUR SCROLLABLE VIEW)
|
|-Container(YOUR FOOTER)
I used expanded
with SinglechildScrollView
over here
Upvotes: 39
Reputation: 859
Although the accepted answer seems to work on mobile devices, problems occur when the width is (much) bigger than the height. When that happens, the IntrinsicHeight
acts like an AspectRatio
, and the height increases so the footer is pushed off the screen.
I think the problem is with the definition used by the IntrinsicHeight
of its internal workings:
... instead size itself to a more reasonable height.
I can confirm that @Rémi's solutions works also in those cases.
It deserves to be a standard widget provided by the Flutter framework.
Upvotes: 0
Reputation: 61
How I solved this was to wrap the fixed Footer
and The SingleChildScrollView
in a Stack
widget then align the Footer
accordingly.
class ScrollableFooter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
children: <Widget>[
// Your body widgets here
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: // Your fixed Footer here,
),
],
);
}
}
Upvotes: 6
Reputation: 17756
Even though the Rémi answer being right, I've actually found an easier way to achieve what I was looking for by just combining the LayoutBuilder
with the IntrinsicHeight
.
class ScrollableFooter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: constraints.copyWith(
minHeight: constraints.maxHeight,
maxHeight: double.infinity,
),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
// Your body widgets here
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: // Your footer widget
),
),
],
),
),
),
);
});
}
}
Upvotes: 17
Reputation: 277037
The difficulty is that Column
and SingleChildScrollView
have a hard time working together because one needs constraints and the other removes them.
The trick is to use a CustomMultiChildLayout
and do the calculations yourself. Helped by MediaQuery
to obtain the size of the keyboard, so that the footer can disappear to leave more room for the content.
Here's a reusable widget that does it for you:
class FooterLayout extends StatelessWidget {
const FooterLayout({
Key key,
@required this.body,
@required this.footer,
}) : super(key: key);
final Container body;
final Container footer;
@override
Widget build(BuildContext context) {
return CustomMultiChildLayout(
delegate: _FooterLayoutDelegate(MediaQuery.of(context).viewInsets),
children: <Widget>[
LayoutId(
id: _FooterLayout.body,
child: body,
),
LayoutId(
id: _FooterLayout.footer,
child: footer,
),
],
);
}
}
enum _FooterLayout {
footer,
body,
}
class _FooterLayoutDelegate extends MultiChildLayoutDelegate {
final EdgeInsets viewInsets;
_FooterLayoutDelegate(this.viewInsets);
@override
void performLayout(Size size) {
size = Size(size.width, size.height + viewInsets.bottom);
final footer =
layoutChild(_FooterLayout.footer, BoxConstraints.loose(size));
final bodyConstraints = BoxConstraints.tightFor(
height: size.height - max(footer.height, viewInsets.bottom),
width: size.width,
);
final body = layoutChild(_FooterLayout.body, bodyConstraints);
positionChild(_FooterLayout.body, Offset.zero);
positionChild(_FooterLayout.footer, Offset(0, body.height));
}
@override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
return true;
}
}
Used as such:
FooterLayout(
body: body,
footer: footer,
),
Upvotes: 7