How to create a scroll view with fixed footer with Flutter?
Asked Answered
T

8

22

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

example

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
                ],
              ),
            ),
          )
        ],
      ),
    );
Taster answered 3/1, 2019 at 17:45 Comment(5)
Can you include a small draft of the desired layout?Cytology
@RémiRousselet done.Taster
I was more thinking of a screenshot/drawing instead. Something visualCytology
@RémiRousselet added.Taster
I found the sliver solution very useful in a similar issue as here: https://mcmap.net/q/586874/-web-like-footer-in-flutter However this one is for unfixed footer with scroll viewEuchology
T
17

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
                  ),
                ),
              ],
            ),
          ),
        ),
      );
    });
  }
}
Taster answered 27/7, 2019 at 0:17 Comment(4)
Brilliant, in my opinion much more clean than responding to MediaQuerys.Shama
you can also use Spacer() instead of ExpandedGrethel
@RomanPanaget in some scenarios won't work properly because you want to assign the available space to the footer widget and align it inside that space at the bottom. With Spacer() you will have another "empty" widget using the space in between and the footer will actually shrink to fit at most its size.Taster
Check it out the result is the sameGrethel
N
39

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

Nailhead answered 21/3, 2021 at 9:7 Comment(5)
Thanks. It's also works for fixed header content. Just need to swap the "YOUR FOOTER" code snippet to the top, before content.Hourigan
Simple and elegant solution ... don't know why it is not accepted as answerTutankhamen
Great solution, thanks!Hirsch
Great solution, I was looking for thisBrazilein
This should be marked as the correct answer. Simple as it can be.Intramundane
T
17

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
                  ),
                ),
              ],
            ),
          ),
        ),
      );
    });
  }
}
Taster answered 27/7, 2019 at 0:17 Comment(4)
Brilliant, in my opinion much more clean than responding to MediaQuerys.Shama
you can also use Spacer() instead of ExpandedGrethel
@RomanPanaget in some scenarios won't work properly because you want to assign the available space to the footer widget and align it inside that space at the bottom. With Spacer() you will have another "empty" widget using the space in between and the footer will actually shrink to fit at most its size.Taster
Check it out the result is the sameGrethel
W
11

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,
          ],
        ),
      ),
    ],
  ),
);
Waylen answered 26/3, 2021 at 0:14 Comment(0)
P
7

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,
),
Prickle answered 3/1, 2019 at 18:44 Comment(1)
There is one issue withfooter: footer, I have included footer: TextField(..) and when click on input my keyboard popup over the footer area and hides the TextField areaEphraimite
L
6

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

Liquescent answered 17/4, 2020 at 23:45 Comment(1)
IMHO still the most simple solution.Underlie
H
1

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")),
          ),
        ],
      ),
    );
Howarth answered 21/4, 2023 at 4:4 Comment(0)
F
0

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.

Forwards answered 4/7, 2020 at 18:44 Comment(0)
L
0

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()],
            ),
          ),
        );
      },
    ),
  ],
);
Larock answered 8/10, 2023 at 2:17 Comment(1)
Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, can you edit your answer to include an explanation of what you're doing and why you believe it is the best approach?Coleman

© 2022 - 2024 — McMap. All rights reserved.