Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to scroll to bottom of SingleChildScrollView when TextField gets focus?

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:

  • Taking advantage of Scaffold's ability to do this automatically (Scaffold is the parent widget of everything in the app)
  • Using this tutorial in which a helper widget is created. Also uses WidgetBindingsObserver, but the tutorial as a whole did not work for me. I wonder if WidgetBindingsObserver could still be helpful, however.

What almost works:

  • Attaching a FocusNode to the TextForm, then attaching a listener in initState() which will animate to the maxScrollExtent when it has focus.

By almost, here's what I mean (excuse the GIF discoloration):

enter image description here

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!

like image 694
Chandler Davis Avatar asked Apr 15 '19 22:04

Chandler Davis


People also ask

How do I scroll automatically in flutter?

You can create a ScrollController and pass it to the controller parameter of your scrolling widget. Then you can use the animateTo method to animate to an offset.


1 Answers

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.

like image 124
diegoveloper Avatar answered Oct 17 '22 17:10

diegoveloper