How to position a Widget at the bottom of a SingleChildScrollView?
Asked Answered
R

9

34

I need to use SingleChildScrollView in order to be able to use keyboard_actions so that i can put a "Done" button on top of the keyboard in iOS (using a numeric keyboard at the moment)

The SingleChildScrollView will have a column as a child and then a button to be placed at the bottom. I tried using LayoutBuilder to enforce a height to the SingleChildScrollView.

LayoutBuilder(
      builder: (BuildContext context, BoxConstraints viewportConstraints) {
    return SingleChildScrollView(
        child: ConstrainedBox(
            constraints:
                BoxConstraints(minHeight: viewportConstraints.maxHeight),
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                mainAxisSize: MainAxisSize.max,
                children: <Widget>[
                  Column(),
                  // Spacer() ?
                  FlatButton()
                ])));
  });

I tried using the BoxConstraints with the maxHeight attribute, but ultimately the widget wouldn't scrooll up when the keyboard appeared.

Side note: the Scaffold has both resizeToAvoidBottomInset and resizeToAvoidBottomPadding set to true (the default value)

Rheingold answered 8/3, 2019 at 16:32 Comment(0)
H
51

Here's how can you can solve the scrolling issue described in the comments. This solution also seems to be more efficient (even if IntrinsicHeight is an expensive widget)

return SingleChildScrollView(
    child: ConstrainedBox(
        constraints: BoxConstraints(
            minWidth: MediaQuery.of(context).size.width,
            minHeight: MediaQuery.of(context).size.height),
        child: const IntrinsicHeight(
          child: Column(
              mainAxisSize: MainAxisSize.max,
              children: [
                Text("Some text"),
                SizedBox(height: 16),
                Spacer(),
                Text("Bottom text"),
              ]
          ),
        )
    )
 );
}
Hydrosphere answered 9/3, 2019 at 5:48 Comment(5)
Thank you! I used this same solution for other parts of my project, but I don't know why I decided to complicate this one.Rheingold
When the keypad opens, it says: "Overflow by 104 pixels." But i guess, SingleChilScrollview should be scrolling. but in this case, scrollview is not scrollingRusell
if you have an AppBar then your FlatButton will be off screenEroticism
This solution is not the most viable, as the widget inside the sizedBox has a limited size as well as the number of items inside, causing screen overflow.Narah
You do not need to query for height to do this in React Native, and you should not have to in Flutter. This is a dirty hack which causes another render.Enchain
A
34

The chosen answer doesn't really work, as using a SizedBox restrict the max size of the Column to the height of the screen, so you can't put as many widgets as you want in it (or they will go off-screen without beeing scrollable).

This solution will work no matter of many widgets are in the column: https://github.com/flutter/flutter/issues/18711#issuecomment-505791677

Important note: It will only work if the widgets in the column have a fixed height (for example TextInputField does NOT have a fixed height). If they have variable a height, wrap them with a Container of fixed height.

Arsenide answered 30/6, 2020 at 17:53 Comment(2)
How to implement inside Stack?Lacework
Don't know why your answer is not the marked one. It works hella great!. Thank you so much.Lithuanian
G
19

I just copied and pasted here the best solution I found, quoted by @Quentin, elaborated by @NikitaZhelonkin in issue 18711 of Flutter on Github. Simply the perfect solution!

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'),
                        ]
                    ),
                  )
              )
          );
        })
    );
  }
}
Graze answered 26/3, 2021 at 15:59 Comment(0)
C
5

The best way is to use CustomScrollView instead of SingleChildScrollView. When using a SizedBox with height, graphical errors will occur when the widget does not fit and a scroll is needed.

CustomScrollView(
    slivers: [
      SliverFillRemaining(
        hasScrollBody: false,
        child: Colum..
Cusack answered 16/3, 2023 at 14:36 Comment(0)
C
1

You may use this workaround appraoch also, You can use padding attribute in SingleChildScrollView like this:

SingleChildScrollView(
padding: EdgeInsets.only(top: height),
child: yourWidgets(),

while height is the distance far away from top.
You can also use this line to get mobile screen height:

double height = MediaQuery.of(context).size.height;
Chukker answered 28/1, 2021 at 9:23 Comment(0)
P
1

Let the code speak.

class SettingsView extends StatelessWidget {
  const SettingsView({super.key});

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverFillRemaining(
          child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                const ColoredBox(
                  color: Colors.redAccent,
                  child: SizedBox(height: 100, width: 100,),
                ),
                const Spacer(), // Fill all the spacing between red and blue boxes
                const ColoredBox(
                  color: Colors.blueAccent,
                  child: SizedBox(height: 100, width: 100,),
                ),
                TextButton(onPressed: () {}, child: const Text('I am a button anchored to the bottom')),
                const SizedBox(height: 20,)
              ]
          ),
        ),
      ],
    );
  }
}

Screenshots:

Portrait

Landscape

Pish answered 28/1 at 1:14 Comment(0)
D
0

The best solution I have found for myself.

Center the widgets and push them from top to bottom with padding.

return Scaffold(
    resizeToAvoidBottomInset: true,
    //resizeToAvoidBottomPadding: false,
    backgroundColor: PrimaryColor,
    body: Center(
    SingleChildScrollView(child:
      _body())));


Widget _body() {
return Padding(
 padding: EdgeInsets.only(top: MediaQuery.of(context).size.height/2.7),
 child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.end,
    children: <Widget>[
      Text(
        'You have pushed the button this many times:',
      ),
      Visibility(
          visible:
              _userProvider.stateReqVMTokenSocial != 
       StateReqVM.Processing,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
           ...
           ...
           ...
            ],
          )),
      Visibility(
          visible:
              _userProvider.stateReqVMTokenSocial == 
         StateReqVM.Processing,
          child: CircularProgressIndicator()),
      Text(
        '',
        style: Theme.of(context).textTheme.headline4,
      ),
    ],
)
 );
}
Dugong answered 22/8, 2021 at 13:8 Comment(0)
I
0

you just add widget inside bottomNavigationBar like this:

 return Scaffold(
    bottomNavigationBar: YOUR_WIDGET()
{...}
Intercrop answered 26/7, 2023 at 13:57 Comment(0)
B
0
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'),
                        ]
                    ),
                  )
              )
          );
        })
    );
  }
}
Bretbretagne answered 5/2 at 11:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.