18

I am attempting to create a SingleChildScrollView with a column in it, and I'd like a button to be at the very bottom of the screen. To do this, I am attempting to use a stack with the SingleChildScrollView and FlatButton as children. What I end up with is this:

enter image description here

I cannot get the button to stick to the bottom, even though I've positioned the button and aligned it to the bottom. The green is just to show the height of the column and that the button is stuck up against the bottom of the column. The Stack, SingleChildScrollView, Column, and FlatButton are only taking up as much space as needed for them to show. I need to stick that button to the bottom of the screen.

This is the code that I'm using to create this. What am I missing?

return Scaffold(
  appBar: AppBar(
    // Here we take the value from the MyHomePage object that was created by
    // the App.build method, and use it to set our appbar title.
    title: Text(widget.title),
  ),
  body: Container(
    width: double.infinity,
    color: Colors.red,
    child: Stack(
      children: <Widget>[
        Container(
          color: Colors.green,
          child: SingleChildScrollView(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.end,
              children: <Widget>[
                Container(
                  child: TextField(
                    decoration: InputDecoration(
                      labelText: 'Protein',
                    ),
                  ),
                  margin: EdgeInsets.only(left: 20, right: 20),
                ),
                Container(
                  child: TextField(
                    decoration: InputDecoration(
                      labelText: 'Fat',
                    ),
                  ),
                  margin: EdgeInsets.only(left: 20, right: 20),
                ),
                Container(
                  child: TextField(
                    decoration: InputDecoration(
                      labelText: 'Fiber',
                    ),
                  ),
                  margin: EdgeInsets.only(left: 20, right: 20),
                ),
                Container(
                  child: TextField(
                    decoration: InputDecoration(
                      labelText: 'Moisture',
                    ),
                  ),
                  margin: EdgeInsets.only(left: 20, right: 20),
                ),
                Container(
                  child: TextField(
                    decoration: InputDecoration(
                      labelText: 'Ash',
                    ),
                  ),
                  margin: EdgeInsets.only(left: 20, right: 20),
                ),
              ],
            ),
          ),
        ),
        Positioned(
          bottom: 0,
          left: 0,
          right: 0,
          child: Align(
            alignment: Alignment.bottomCenter,
            child: ButtonTheme(
              minWidth: double.infinity,
              height: 50,
              child: FlatButton(
                color: Colors.blueAccent.shade400,
                onPressed: () {},
                child: Text(
                  'Calculate Carbs',
                  style: TextStyle(
                    fontSize: 20,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ),
        )
      ],
    ),
  ),
);

Edit: Both of the methods given below work for expanding the Stack to fill the entire screen. I added additional TextField widgets to fill up the screen and then clicked the bottom one to make sure that the bottom widget would be visible when the keyboard was open and noticed that the button covered the bottom field. It's like the scroll view is scrolling up the correct amount by is ignoring that there's a button there.

In the images below, I tapped on the End Field 3 widget. The button is covering it so that I cannot see what I'm entering into the field.

Keyboard Collapsed Keyboard Expanded

Scott Kilbourn
  • 1,545
  • 6
  • 18
  • 40
  • you probably already solve this, but just in case, you can use ` resizeToAvoidBottomInset: false` on your scaffold to avoid this – user12208004 Jan 27 '20 at 10:57
  • 1
    How did you solve this? I am struggling with the same problem. Setting the height manually breaks scolling on text inputs – rnli Apr 14 '20 at 21:44

7 Answers7

17

Use CustomScrollView:

CustomScrollView(
  slivers: [
    SliverFillRemaining(
      hasScrollBody: false,
      child: Column(
        children: <Widget>[
          const Text('Header'),
          Expanded(child: Container(color: Colors.red)),
          const Text('Footer'),
        ],
      ),
    ),
  ],
)

See: https://stackoverflow.com/a/62097942/5164462

User Rebo
  • 3,056
  • 25
  • 30
7

Option 1 with floating action button

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Column(),
      ),
      floatingActionButton: YourButtonWidget(),
      floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
    );
  }

Option 2 with the bottom navigation bar

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Column(),
      ),
      bottomNavigationBar: YourButtonWidget(),
    );
  }
Bhargav Sejpal
  • 1,352
  • 16
  • 23
1

The reason for such behavior is the size of the parent container. Just change the container size

      body: Container(
        width: MediaQuery.of(context).size.width,
        height: MediaQuery.of(context).size.height,
        color: Colors.red,
        child: Stack(
          children: <Widget>[

and you are done.

Explanation, if you are curious :) If we look to the Stack widget we can find this description

   /// By default, the non-positioned children of the stack are aligned by their
  /// top left corners.

So, this widget takes only that space, that was given by the parent widget. And you used the container without size. All children widget took their place automatically and Alin worked inside this automatically created container.

awaik
  • 10,143
  • 2
  • 44
  • 50
0

Keep the button at the bottom always,

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Expanded(
              child: Padding(
                padding: const EdgeInsets.all(15.0),
                child: new Form(
                  key: _formKey, 
                  child: SingleChildScrollView(
                    child: Column(
                      children: [
                         _currntForm()
                      ],
                    )
                  )),
              )),
           Padding(
            padding: const EdgeInsets.all(15.0),
            child: FullWidthIconBotton(
              icon: Icon(LinearIcons.floppy_disk),
              label: 'SAVE',
              color: Colors.green,
              onClick: () {
                _validateFormFields();
              },
            ) ,
          ) 
        ],
      ),
    );
  }
Aathi
  • 2,599
  • 2
  • 19
  • 16
0

I solved this by catch keyboard showing and adjust the height of the container if the keyboard show

bool isKeyboardShowing = MediaQuery.of(context).viewInsets.vertical > 0;
@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Container(
          height: isKeyboardShowing
              ? MediaQuery.of(context).size.height - 100
              : MediaQuery.of(context).size.height - 300,
          child: Column(),
        ),
      ),
      bottomNavigationBar: YourButtonWidget(),
    );
  }

I think it's not the best solution, yet this workaround works for me.

0

I was facing similar problem so i used bottomNavigationBar property of Scaffold, This how my bottomNavigationBar code looks like.

bottomNavigationBar: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        ElevatedButton(
          onPressed: () {},
          child: const Text("Skip"),
        ),
        ElevatedButton(
          onPressed: () {},
          child: const Text("Next"),
        ),
      ],
    ),
  ),
Hitesh Patel
  • 183
  • 2
  • 12
-3

add this param to the stack: fit: StackFit.expand,

Sergio Bernal
  • 2,126
  • 9
  • 20