So, I have a login page with two TextFields, and then a RaisedButton for login at the very bottom. When I tap on the email field and the keyboard pops up, I would like for the SingleChildScrollView (the parent of everything on the page) to scroll to the maxScrollExtent.
Things I have tried that haven't worked:
What almost works:
By almost, here's what I mean (excuse the GIF discoloration):
As you can see, it doesn't work the first time it focuses so I have to tap the password field, then retap the email field for it to animate. I have tried adding a delay (even up to 500ms) so that the viewport has time to fully resize before doing this, but that didn't work either.
If you recognize this login theme, that's because I adapted it from here. The file is pretty lengthy, but here are the relevant bits:
@override
void initState() {
super.initState();
scrollController = ScrollController();
focusNode = FocusNode();
focusNode.addListener(() {
if (focusNode.hasFocus) {
scrollController.animateTo(scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 500), curve: Curves.ease);
}
});
_emailFieldController = TextEditingController();
_passFieldController = TextEditingController();
_emailFieldController.addListener(() {
_emailText = _emailFieldController.text;
});
_passFieldController.addListener(() {
_passText = _passFieldController.text;
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
controller: scrollController,
child: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: Colors.white,
image: DecorationImage(
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.05), BlendMode.dstATop),
image: AssetImage('assets/images/mountains.jpg'),
fit: BoxFit.cover,
),
),
child: new Column(
children: <Widget>[
// this is where all other widgets in the file are
Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.only(left: 40.0, right: 40.0, top: 10.0),
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.deepPurple,
width: 0.5,
style: BorderStyle.solid),
),
),
padding: const EdgeInsets.only(left: 0.0, right: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: TextField(
controller: _emailFieldController,
keyboardType: TextInputType.emailAddress,
focusNode: focusNode,
obscureText: false,
textAlign: TextAlign.left,
decoration: InputDecoration(
border: InputBorder.none,
hintText: '[email protected]',
hintStyle: TextStyle(color: Colors.grey),
),
),
),
],
),
),
Any guidance would be greatly appreciated. Thank you!
Use addPostFrameCallback
to listen after the widget was built.
_onLayoutDone(_){
FocusScope.of(context).requestFocus(focusNode);
}
@override
void initState() {
//... your stuff
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}
UPDATE
I see the error, the first time you use scrollController.position.maxScrollExtent
the value is 0, after you tap on password textField and you change the focus to email
, now the maxScrollExtent
is different because the keyboard is open.
If you want to make it work, do a logic to calculate the space and set the value directly.
If you use
scrollController.animateTo(180.0,
duration: Duration(milliseconds: 500), curve: Curves.ease);
It should work.