Center widget vertically inside a SingleChildScrollView - flutter

I'm new to Flutter so I train myself by making a simple form. I realized while I was debugging on my iPhone the virtual keyboard triggered an error: "A RenderFlex overflowed by 29 pixels on the bottom". I fixed this issue by wrapping my Container inside a SingleChildScrollView.
The problem now is my Column's content is no longer centered. I can't figure out why ...
Here's my code to help you to understand :
List<Widget> _buildBody() {
var listWidget = List<Widget>();
SingleChildScrollView singleChild = SingleChildScrollView(
padding: EdgeInsets.only(top: 1.0),
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.all(30.0),
padding: EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 50.0),
child: Image.asset(
'assets/github.png',
width: 100.0,
height: 100.0,
),
),
Container(
margin: EdgeInsets.only(bottom: 10.0),
child: TextFormField(
controller: _usernameController,
autofocus: true,
decoration: InputDecoration(
hintText: 'Username',
suffixIcon: Icon(Icons.account_circle)))),
Container(
child: TextFormField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(
hintText: 'Password', suffixIcon: Icon(Icons.vpn_key)),
),
),
Container(
margin: EdgeInsets.only(top: 10.0),
child: RaisedButton(
splashColor: Colors.greenAccent,
color: Colors.blue,
child: Text('Submit'),
onPressed: () {
_handleSubmit();
},
),
)
],
),
),
));
listWidget.add(singleChild);
if (_requesting) {
var modal = new Stack(
children: [
new Opacity(
opacity: 0.3,
child: const ModalBarrier(dismissible: false, color: Colors.grey),
),
new Center(
child: new CircularProgressIndicator(),
),
],
);
listWidget.add(modal);
}
return listWidget;
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Github Login'),
),
body: Stack(
children: _buildBody(),
));
}
I added the property "mainAxisAlignment: MainAxisAlignment.center" to my Column. It worked well before I wrapped it into the SingleChildScrollView.
If someone could help me and explain me why it doesn't work anymore I would really appreciated it :)

ArtiomLK Suggested a solution in comments which helped me:
wrap SingleChildScrollView in a Center. The widgets tree is:
Center( child: SingleChildScrollView( child: Column(...)))
None of the others helped.

Solution:
Put your top level Stack inside Center widget.
body: Center(child: Stack(
children: _buildBody(),
)));
Tip to debug:
Use Flutter Inspector to find where the layout is going wrong.
I edited your code a bit(to make to work in my local) and then I inspected. It showed like below
We have a Stack and SingleChildScrollView as per code(refer to the right side of the diagram where the stack of widgets are displayed). As size is determined by SingleChildScrollView(contents inside it), Stack occupies only a little space and by default, it aligned at top. So put it under Center, the whole Stack view will come in the center.

You can use "Center" widget like as
return Center(
child: SingleChildScrollView()
)

There's a section about it in the official docs:
Using SingleChildScrollView with a Column
Source: https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html
However I found that using a combination of the above worked better. Namely confining the Column to the minimum size possible (MainAxisSize.min). This should be fine, as the SingleChildScrollView will take up as much space as is possible on screen, before scrolling can happen.
Widget _buildPortraitPage() {
return Center(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min, // <-- notice 'min' here. Important
children: [
Flexible(
child: Image.asset('assets/images/my-image.png'),
),
Flexible(
child: Text('My content'),
),
],
),
),
);
}
The layout engine in Flutter is pretty hard to figure out.

use LayoutBuilder widget to wrap all the widget and use Spacer widget before and after your centered widget inside the Column
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: [
Spacer(),
Container(
child: //your widget
),
Spacer()
]
),
)
)
);
})
);
}
}

Just wrap the Column in a Center. I used that for my apps and it seems to center the contents of my Column even inside a SingleChildScrollView.

There's another way to achieve this:
Center(
child: ListView(
shrinkWrap: true,
children: []
)
)

wrap SingleChildScrollView with center
child: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,

use SingleChildScrollView widget
body: Center(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,

You can use SingleChildScrollView -> ConstrainedBox(with min height and width) -> Center

Wrap your SingleChildScrollView(), with center. and here you go.

Related

How to shrink first child in a Column?

I had a strange bug with a layout of a Column, after which I decided to check out documentation - was surprised to to see how Flex lays out zero-flex elements. Anyway, here is the question.
How do you shrink the top element, i.e. layout it with the minimal possible size? I.e. that can be a top menu with content pane following it below,
Column(
children: [
/// menu is given unlimited constraints, so it may try to expand or may fail to layout
menu,
Expanded(child: content)
]
)
Codepen Demo showcases the problem
Another way to formulate the question: how do you keep dominance of the Expanded widget? Here, the LimitedBox can eat as much space as it wants. This is one of the problems of the Column widget.
Try this,let me know if it works for you
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
height: MediaQuery.of(context).size.height*0.1,
width: double.infinity,
color: Colors.amber,
child: Center(child: Text('menu'))),
Column(children: [Text('wow')]),
Text(
'wow',
),
]),
));
Use ConstrainedBox and give maxHeight a value of maximum available space.
LayoutBuilder(
builder: (_, constraints) {
return Column(
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: constraints.maxHeight,
),
child: Text('Your menu'),
),
Expanded(
child: Container(color: Colors.blue),
),
],
);
},
)
When you don't set size for widget in column It use minimum size as it required. I tested your code and removed LimitedBox,SizedBox and Spacer. I just wrap your Text with Container to show result easily.
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(children: [
Container(color: Colors.red, child: Center(child: Text('menu'))),
Column(children: [Text('wow')]),
Expanded(
child: Center(
child: Text(
'wow',
),
))
]),
),
);
}

Make a column in a ListView/SingleChildScrollView take up the REMAINING space of a screen

I am trying to build a registration screen, the concept that I have designed on adobe XD is that the registration Screen will have the app logo in the top center, a card with approximately 3-4 textfields/areas, a button in the bottom center. Since there will be multiple cards to fill I want a circular page indicator in the center to make it easier for the user to track the remaining data to fill as well as the user wont have to fill a really long list in a single screen.
What I have tried is
A SingleChildScrollView with a column inside it and the column has the first registration form to fill and another column inside it that has the button with the page indicator
A Stack and a pageviewbuilder this gave me the best results in terms of layout but the only issue is that when using the keyboard the widgets will throw a renderFlew overflow
and right now a listview with the form in it and a column that has the button and the page indicator
Things that I need:
the keyboard to not cause an issue with form
the code to be practical and consistent for multiple screen sizes
my latest code
#override
Widget build(BuildContext context) {
List<Widget> registrationForms = [EmailRegistrationForm()];
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: ListView(
children: <Widget>[
Align(alignment: Alignment.center, child: registrationForms[0]),
Container(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 5),
child: InkWell(
child: Text("Already have an account? Login!"),
),
),
PrimaryStyledButton(text: "Next", onPress: () {}),
CirclePageIndicator(
currentPageNotifier: _currentPageNotifier,
itemCount: registrationForms.length,
selectedDotColor: Theme.of(context).accentColor,
)
],
),
),
],
));
}
what is expected
so this is what ended up working for me, I realized that the list view is kinda useless so the structure became as follows:
SingleChildScrollView that has a sized box (size of the screen) and a column inside the sized box, the SingleChildScrollView is to stop the issue of the keyboard pushing things up as well giving the ability to scroll while filling the data
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 10),
child: landingScreen.getLogoWidget(),
),
Expanded(
child: Align(
alignment: Alignment.center, child: registrationForms[0]),
),
Padding(
padding: EdgeInsets.only(bottom: 10),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 5),
child: InkWell(
child: Text(ALREADY_REGISTERED_MSG),
),
),
PrimaryStyledButton(text: NEXT, onPress: () {
}, ),
Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: CirclePageIndicator(
currentPageNotifier: _currentPageNotifier,
itemCount: registrationForms.length,
selectedDotColor: Theme.of(context).accentColor,
),
)
],
),
),
],
),
),
));
Try using SingleChildScrollView with expanded widget
#override
Widget build(BuildContext context) {
List<Widget> registrationForms = [EmailRegistrationForm()];
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: SingleChildScrollView(
child: Expanded(
child: Column(
children: <Widget>[
Align(alignment: Alignment.center, child: registrationForms[0]),
Container(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 5),
child: InkWell(
child: Text("Already have an account? Login!"),
),
),
PrimaryStyledButton(text: "Next", onPress: () {}),
CirclePageIndicator(
currentPageNotifier: _currentPageNotifier,
itemCount: registrationForms.length,
selectedDotColor: Theme.of(context).accentColor,
)
],
),
),
],
),
),
),
);
}

Flutter using wrap_content for TabBarView

in this below code i have Wrap widget into TabBarView and i'm not sure as this widget size, when i use Container with specify height my problem solved, but i miss some layouts which i want to show that with map, how can i resolve this problem? i tested SizedBox.expand but it doesn't any change
Flexible(
child: TabBarView(
children: [
Center(
child: Wrap(
children: feeds.map((feed){
return Container(
width: MediaQuery.of(context).size.width /3.5,
height:MediaQuery.of(context).size.width /3.5,
margin: EdgeInsets.all(2.0),
child: GridTile(
header: feed.feedType ==1?Icon(Icons.video_call)
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(3.0)
child: Image.asset(feed.feedImage))),
),
);
}).toList(),
),
),
],
controller: tabController,
),
),

How to position a Widget at the bottom of a SingleChildScrollView?

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)
The issue with SingleChildScrollView is that it shrikwrap it's children.
So to have auto size widget in between - we need to use MediaQuery to get the screen height & SizedBox to expand - SingleChildScrollView.
Here Button will be at bottom of screen.
working Code:
double height = MediaQuery.of(context).size.height;
SingleChildScrollView(
child: SizedBox(
height: height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Column(
children: <Widget>[
Text('Dummy'),
Text('Dummy'),
Text('Dummy'),
],
),
Spacer(),
FlatButton(
onPressed: () {},
child: Text('Demo'),
)
])),
)
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.
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'),
]
),
)
)
);
})
);
}
}
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;
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,
),
],
)
);
}

When keyboard is shown, everything is pushed up and I get a error

I have the following code:
class _MyHomePageState extends State {
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return new Scaffold(
body: new Column(
children: [
new Container(
color: JBTheme.colorGreenBrand,
height: 130.0,
alignment: Alignment.bottomLeft,
child: new Center(
child: new Align(
alignment: Alignment.bottomCenter,
child: new Container(
color: JBTheme.colorOrange,
constraints: new BoxConstraints(maxWidth: 270.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
new Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
new Image.asset("flags/flag_dk.png"),
new Container(
margin: const EdgeInsets.only(top: 4.0, bottom: 4.0),
child: new Text("Danmark"),
),
new Text("DKK")
],
),
new Expanded(child: new Image.asset("images/arrow.png")),
new Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
new Image.asset("flags/flag_us.png"),
new Container(
margin: const EdgeInsets.only(top: 4.0, bottom: 4.0),
child: new Text("USA"),
),
new Text("USD")
],
)
],
),
),
),
),
),
new Expanded(
child: new Container(
color: JBTheme.colorGreyMedium,
child: new Column(
children: [
new Expanded(child: new Container()),
new Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: new Card(
elevation: -8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
child: new Container(
width: 200.0,
height: 30.0,
color: JBTheme.colorWhite,
child: new Stack(
children: [
new TextField(
textAlign: TextAlign.center
)
],
)
),
)
)
],
),
)),
new Container(
height: 260.0,
color: JBTheme.colorGreenMint,
)
],
));
}
}
And it looks like this:
But when I click the TextField and the keyboard opens I get this:
I did not expect all of my layout to be moved up by the full keyboard height, I would like it to only move up enough so that the focused TextField is visible (and not to give a error). How can I fix this, and why is it happening?
Thank you
Søren
There are two solutions to this problem.
Add resizeToAvoidBottomPadding: false to your Scaffold
Scaffold(
resizeToAvoidBottomPadding: false,
body: ...)
Put your Scaffold body inside a scrollableView (like SingleChildScrollView or ListView)
new Scaffold(
body: SingleChildScrollView(child: //your existing body
...)
You can find similar problem and answer here
This is Flutters way of showing us how many pixels of content will be hidden from users eyesight when using the keypad. Try setting resizeToAvoidBottomPadding to false... From docs:
Whether the body (and other floating widgets) should size themselves
to avoid the window's bottom padding.
Scaffold(
resizeToAvoidBottomPadding: false,
This will avoid the resizing so you will at least avoid the dev warning being shown. But remember user will not see some content, and also this is a developer warning.
Update on 17/10/2019
resizeToAvoidBottomPadding is now Deprecated.
Use resizeToAvoidBottomInset to specify if the body should resize when the keyboard appears.
Scaffold(
resizeToAvoidBottomInset: false,
If you just use SingleChildScrollView content looses vertical alignment. You can also wrap the SingleChildScrollView in a Center Widget.
child: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>...
A full description of this error and two possible solutions can be found on Medium article available on https://medium.com/zipper-studios/the-keyboard-causes-the-bottom-overflowed-error-5da150a1c660. The last one works great for me.