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

Flutter Quill编辑器无法随用户输入自动滚动问题求助

解决flutter_quill嵌套SliverFillRemaining时输入不自动滚动的问题

问题根源

  1. 每次调用buildQuillEditorWidget都会重新创建QuillController和ScrollController,导致状态丢失,无法持续跟踪光标位置。
  2. 编辑器设置了NeverScrollableScrollPhysics(),且SliverFillRemaining的hasScrollBody: false,使得编辑器自身无法滚动,同时没有将光标位置变化同步到父级CustomScrollView的滚动控制器。

解决方案

  1. 将QuillController和父级CustomScrollView的ScrollController提升到页面State中,避免每次重建丢失状态。
  2. 监听QuillController的选区变化,当光标位置更新时,计算对应的滚动偏移量,让父级滚动控制器自动滚动到光标可视位置。
  3. 调整SliverFillRemaining的配置,确保滚动上下文正确传递。

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: ""),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final quill.QuillController _bodyController;
  late final ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _bodyController = quill.QuillController.basic();
    _scrollController = ScrollController();
    
    // 监听光标选区变化,自动滚动到光标位置
    _bodyController.addListener(_handleSelectionChange);
  }

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

  void _handleSelectionChange() {
    final selection = _bodyController.selection;
    if (selection.isValid && selection.isCollapsed) {
      // 获取光标所在位置的RenderObject
      final editorContext = _bodyController.editorKey.currentContext;
      if (editorContext != null) {
        final renderBox = editorContext.findRenderObject() as RenderBox;
        final localOffset = _bodyController.getOffsetForCaret(selection.base);
        
        // 让父级滚动控制器确保光标位置可视
        _scrollController.position.ensureVisible(
          renderBox,
          alignment: 0.3,
          duration: const Duration(milliseconds: 200),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        controller: _scrollController,
        slivers: [
          SliverFillRemaining(
            hasScrollBody: true, // 改为true,允许子组件参与滚动上下文
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.purple,
                child: quill.QuillEditor.basic(
                  controller: _bodyController,
                  configurations: const quill.QuillEditorConfigurations(
                    placeholder: "Body",
                    // 可选:保留NeverScrollableScrollPhysics完全依赖父滚动,或移除让编辑器自身滚动
                    // scrollPhysics: NeverScrollableScrollPhysics(),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

关键调整说明

  • 将控制器移到State的initState中初始化,确保状态持久化,避免每次build重建丢失数据。
  • 添加_handleSelectionChange监听方法,当光标位置变化时,通过父级ScrollController的ensureVisible方法自动滚动到光标可视区域。
  • 将SliverFillRemaining的hasScrollBody设为true,确保滚动上下文能正确传递给子组件。
  • 可根据需求选择是否保留编辑器的NeverScrollableScrollPhysics:保留则完全依赖父级滚动,移除则编辑器自身可处理局部滚动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:20:12