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

Flutter:如何联动两个不同滚动控制器的滚动行为?

实现TextField与ListView的滚动同步

要实现两个滚动组件的同步滚动,核心思路是监听两个滚动控制器的滚动事件,在一方滚动时驱动另一方同步到对应位置。注意:TextField的滚动需要使用ScrollController而非TextEditingController,TextEditingController仅负责文本内容与光标管理,不控制滚动行为。

具体实现步骤:

  1. 定义滚动控制器与同步标记:
    添加_textFieldScrollController用于控制TextField的滚动,同时用_isSyncing标记避免滚动事件循环触发。

  2. 绑定滚动监听器:
    给两个控制器添加监听器,当其中一个滚动时,计算目标位置并驱动另一个控制器同步滚动,通过标记阻止循环触发。

  3. 修正TextField配置:
    给TextField添加scrollController属性,绑定对应的滚动控制器,确保其滚动行为可被控制。

完整代码示例:

import 'package:flutter/material.dart';

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

  @override
  State<NotePage> createState() => _NotePageState();
}

class _NotePageState extends State<NotePage> {
  final TextEditingController _textEditingController = TextEditingController();
  final ScrollController _scrollController = ScrollController();
  final ScrollController _textFieldScrollController = ScrollController();
  bool _isSyncing = false;
  int lines = 10;

  @override
  void initState() {
    super.initState();
    // 绑定滚动同步监听
    _scrollController.addListener(_syncScroll);
    _textFieldScrollController.addListener(_syncScroll);
  }

  void _syncScroll() {
    if (_isSyncing) return;
    _isSyncing = true;

    // 同步两个控制器的滚动位置
    if (_scrollController.hasClients && _textFieldScrollController.hasClients) {
      final targetPos = _scrollController.position.pixels != _textFieldScrollController.position.pixels
          ? _scrollController.position.pixels
          : _textFieldScrollController.position.pixels;
      
      _scrollController.jumpTo(targetPos);
      _textFieldScrollController.jumpTo(targetPos);
    }

    // 延迟释放标记,避免快速滚动冲突
    Future.delayed(const Duration(milliseconds: 50), () {
      _isSyncing = false;
    });
  }

  bool isAtLastLine(TextSelection selection) {
    final text = _textEditingController.text;
    final lineCount = text.split('\n').length;
    final currentLine = text.substring(0, selection.end).split('\n').length;
    return currentLine >= lineCount;
  }

  @override
  void dispose() {
    // 销毁控制器,避免内存泄漏
    _textEditingController.dispose();
    _scrollController.dispose();
    _textFieldScrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          SizedBox(
            height: ((lines * 15) < MediaQuery.of(context).size.height)
                ? MediaQuery.of(context).size.height
                : (lines * 15),
            child: ListView.builder(
              controller: _scrollController,
              itemCount: lines,
              itemBuilder: (context, index) => const Padding(
                padding: EdgeInsets.only(top: 5, bottom: 5),
                child: Divider(
                  color: Colors.black,
                  height: 30,
                  thickness: 1.5,
                ),
              ),
            ),
          ),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 10).copyWith(top: 5),
            child: TextField(
              style: const TextStyle(
                fontFamily: "WorkSans",
                fontSize: 26,
                fontStyle: FontStyle.italic,
                height: 1.55,
              ),
              decoration: const InputDecoration(
                border: InputBorder.none,
              ),
              cursorColor: Colors.black,
              controller: _textEditingController,
              scrollController: _textFieldScrollController, // 绑定TextField的滚动控制器
              maxLines: lines,
              onChanged: (value) {
                final selection = _textEditingController.selection;
                if (isAtLastLine(selection)) {
                  setState(() {
                    lines++;
                  });
                }
              },
            ),
          ),
        ],
      ),
    );
  }
}

关键说明:

  • 避免循环触发:通过_isSyncing标记,在同步过程中暂时屏蔽监听器,防止两个控制器互相触发滚动事件导致死循环。
  • 滚动位置同步:使用jumpTo实现瞬时同步,若需要平滑滚动效果,可替换为animateTo并配置动画参数。
  • 生命周期管理:在组件销毁时必须释放所有控制器,避免内存泄漏问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 10:17:04