TextField 获得焦点时如何滚动到 SingleChildScrollView 的底部?

Posted

技术标签:

【中文标题】TextField 获得焦点时如何滚动到 SingleChildScrollView 的底部?【英文标题】:How to scroll to bottom of SingleChildScrollView when TextField gets focus? 【发布时间】:2019-09-05 23:08:37 【问题描述】:

所以,我有一个带有两个 TextField 的登录页面,然后在最底部有一个用于登录的 RaisedButton。当我点击电子邮件字段并弹出键盘时,我希望 SingleChildScrollView(页面上所有内容的父级)滚动到 maxScrollExtent。

我尝试过但没有奏效的方法:

利用 Scaffold 自动执行此操作的能力(Scaffold 是应用中所有内容的父小部件) 使用this tutorial 在其中创建了一个帮助小部件。也使用 WidgetBindingsObserver,但整个教程对我不起作用。不过,我想知道 WidgetBindingsObserver 是否仍然有用。

几乎可行的方法:

将 FocusNode 附加到 TextForm,然后在 initState() 中附加一个侦听器,该侦听器将在 maxScrollExtent 获得焦点时进行动画处理。

差不多,这就是我的意思(请原谅 GIF 变色):

如您所见,它在第一次聚焦时不起作用,因此我必须点击密码字段,然后重新点击电子邮件字段以使其动画化。我尝试添加延迟(甚至最多 500 毫秒),以便视口有时间在执行此操作之前完全调整大小,但这也不起作用。

如果你认出这个登录主题,那是因为我改编自 here。该文件很长,但以下是相关位:

@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: 'coolname@bestemail.com',
                        hintStyle: TextStyle(color: Colors.grey),
                      ),
                    ),
                  ),
                ],
              ),
            ),

任何指导将不胜感激。谢谢!

【问题讨论】:

【参考方案1】:

在构建小部件后使用addPostFrameCallback 进行监听。

          _onLayoutDone(_)
              FocusScope.of(context).requestFocus(focusNode);
           

          @override
          void initState() 
            //... your stuff

            WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
            super.initState();
          

更新

我看到了错误,第一次使用 scrollController.position.maxScrollExtent 时,值是 0,在您点击密码 textField 并将焦点更改为 email 后,现在 maxScrollExtent 不同,因为键盘是打开的。

如果你想让它工作,做一个逻辑来计算空间并直接设置值。

如果你使用

 scrollController.animateTo(180.0,
        duration: Duration(milliseconds: 500), curve: Curves.ease);

它应该可以工作。

【讨论】:

按照描述尝试了这个,并在其中使用了 FocusNode.addListener(),但不幸的是没有改变。 不要在_onLayoutDone里面使用FocusNode.addListener(),直接调用scrollController.animateTo... 或者直接调用:FocusScope.of(context).requestFocus(focusNode);在 _onLayoutDone 里面 如果你能分享你的部分代码会很棒,这样我就可以自己测试了 没问题。 Here is the link to the file in the repo。我目前的所有工作都在开发分支中。感谢您的帮助!

以上是关于TextField 获得焦点时如何滚动到 SingleChildScrollView 的底部?的主要内容,如果未能解决你的问题,请参考以下文章

导航到另一个屏幕后,TextField 获得焦点

ScrollView中的Horizo​​ntal RecyclerView:垂直滚动时如何获得焦点?

当文本字段获得焦点时,如何使底部工具栏浮在软键盘上方

如何防止滚动到焦点 EditText [重复]

当initialValues通过\绑定到props.object时,如何使用Formik表单将焦点设置在TextField\input上?

Swing下,鼠标点击组件不是自动获得焦点的么?