You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:点击Done按钮隐藏键盘后,让SingleChildScrollView自动滚回初始偏移

问题:点击Done按钮后让SingleChildScrollView滚动回初始位置

我在SingleChildScrollView内放置了一个包含两个TextField的堆叠Card,需求是在两个TextField填写完成并点击Done按钮后,让SingleChildScrollView立刻滚动回initialScrollOffset。目前的实现未达到预期效果——只有手动向下滚动Card时,autoBackToPosition函数才会触发。

演示动图

当前代码:

class Takok extends StatefulWidget {
  const Takok({super.key});

  @override
  State<Takok> createState() => _TakokState();
}

class _TakokState extends State<Takok> with TickerProviderStateMixin {
  TextEditingController user = TextEditingController();
  TextEditingController pass = TextEditingController();
  ScrollController _scrollController = ScrollController();
  void autoBackToPosition() {
    if (_scrollController.offset > 100) {
      _scrollController.animateTo(_scrollController.initialScrollOffset,
          duration: const Duration(milliseconds: 500),
          curve: Curves.decelerate);
    }
  }

  @override
  void initState() {
    _scrollController.addListener(autoBackToPosition);
    super.initState();
  }

  @override
  void dispose() {
    user.dispose();
    pass.dispose();
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
          body: Stack(
        children: [
          Positioned.fill(
              child: Container(
            color: Colors.amber,
          )),
          Align(
            alignment: Alignment.bottomCenter,
            child: SingleChildScrollView(
              controller: _scrollController,
              physics: const BouncingScrollPhysics(),
              child: Container(
                margin: EdgeInsets.only(
                    top: MediaQuery.of(context).size.height * .32),
                height: MediaQuery.of(context).size.height,
                child: Card(
                  elevation: 6,
                  margin: const EdgeInsets.symmetric(horizontal: 20),
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(24)),
                  child: Padding(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
                    child: Column(
                      children: [
                        SizedBox(
                          height: 50,
                        ),
                        TextField(
                          textInputAction: TextInputAction.next,
                        ),
                        SizedBox(
                          height: 20,
                        ),
                        TextField(),
                        SizedBox(
                          height: 30,
                        ),
                        Spacer()
                      ],
                    ),
                  ),
                ),
              ),
            ),
          )
        ],
      )),
    );
  }
}

问题原因

你把回滚逻辑绑定在了ScrollController的listener上,只有发生滚动行为时才会触发这个函数,但点击Done按钮并不会触发滚动事件,所以回滚逻辑不会自动执行。

修复方案

  1. 给第二个TextField设置textInputAction: TextInputAction.done,让键盘显示Done按钮;
  2. 添加onSubmitted回调,在用户点击Done时主动调用回滚函数;
  3. 移除ScrollController的listener,避免不必要的触发。

修改后的完整代码:

class Takok extends StatefulWidget {
  const Takok({super.key});

  @override
  State<Takok> createState() => _TakokState();
}

class _TakokState extends State<Takok> with TickerProviderStateMixin {
  TextEditingController user = TextEditingController();
  TextEditingController pass = TextEditingController();
  ScrollController _scrollController = ScrollController();

  void autoBackToPosition() {
    if (_scrollController.offset > 100) {
      _scrollController.animateTo(
        _scrollController.initialScrollOffset,
        duration: const Duration(milliseconds: 500),
        curve: Curves.decelerate,
      );
      // 可选:收起键盘
      FocusScope.of(context).unfocus();
    }
  }

  @override
  void initState() {
    // 移除滚动监听,改为在Done点击时触发
    super.initState();
  }

  @override
  void dispose() {
    user.dispose();
    pass.dispose();
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Stack(
          children: [
            Positioned.fill(child: Container(color: Colors.amber)),
            Align(
              alignment: Alignment.bottomCenter,
              child: SingleChildScrollView(
                controller: _scrollController,
                physics: const BouncingScrollPhysics(),
                child: Container(
                  margin: EdgeInsets.only(
                    top: MediaQuery.of(context).size.height * .32,
                  ),
                  height: MediaQuery.of(context).size.height,
                  child: Card(
                    elevation: 6,
                    margin: const EdgeInsets.symmetric(horizontal: 20),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(24),
                    ),
                    child: Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 20,
                        vertical: 6,
                      ),
                      child: Column(
                        children: [
                          const SizedBox(height: 50),
                          TextField(
                            textInputAction: TextInputAction.next,
                            controller: user, // 绑定控制器
                          ),
                          const SizedBox(height: 20),
                          TextField(
                            textInputAction: TextInputAction.done, // 设置为Done按钮
                            controller: pass, // 绑定控制器
                            onSubmitted: (value) {
                              // 点击Done时触发回滚
                              autoBackToPosition();
                            },
                          ),
                          const SizedBox(height: 30),
                          const Spacer()
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

关键修改点

  • 第二个TextField添加textInputAction: TextInputAction.done,让键盘显示Done按钮;
  • 添加onSubmitted回调,在用户点击Done时直接调用autoBackToPosition;
  • 移除initState中的滚动监听,避免滚动时误触发回滚;
  • 可选:在回滚函数中添加FocusScope.of(context).unfocus(),点击Done后自动收起键盘,体验更流畅。

内容的提问来源于stack exchange,提问作者pras

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 23:14:56