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

Flutter 同一Row中两个ListView.separated滚动报错及索引联动如何实现?

问题原因

你遇到的报错是因为同一层级的多个垂直滚动ListView默认会复用全局的PrimaryScrollController,同一个控制器不能同时绑定多个滚动位置,所以触发了异常。

解决方案

第一步:修复滚动报错

给两个ListView.separated分别配置独立的ScrollController,同时设置primary: false,禁止使用默认主控制器即可解决报错。

第二步:实现双向索引联动

通过滚动监听实现两个视图的位置同步,注意添加锁标志位避免滚动回调互相触发导致死循环,以下是完整可运行的修改后代码:

class Mainview extends StatefulWidget {
  const Mainview({Key? key}) : super(key: key);

  @override
  _MainviewState createState() => _MainviewState();
}

class _MainviewState extends State<Mainview> {
  // 侧边缩略栏滚动控制器
  final ScrollController _thumbController = ScrollController();
  // 主视图滚动控制器
  final ScrollController _mainController = ScrollController();
  // 联动锁,避免滚动回调互相触发死循环
  bool _isControlling = false;

  @override
  void initState() {
    super.initState();
    // 监听缩略栏滚动
    _thumbController.addListener(() {
      if (_isControlling) return;
      _isControlling = true;
      // 按比例计算主视图应该滚动的位置
      final ratio = _thumbController.offset / _thumbController.position.maxScrollExtent;
      _mainController.jumpTo(ratio * _mainController.position.maxScrollExtent);
      _isControlling = false;
    });
    // 监听主视图滚动
    _mainController.addListener(() {
      if (_isControlling) return;
      _isControlling = true;
      // 按比例计算缩略栏应该滚动的位置
      final ratio = _mainController.offset / _mainController.position.maxScrollExtent;
      _thumbController.jumpTo(ratio * _thumbController.position.maxScrollExtent);
      _isControlling = false;
    });
  }

  @override
  void dispose() {
    _thumbController.dispose();
    _mainController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white.withOpacity(0.7),
      child: Row(
        children: [
          // 侧边缩略栏
          Expanded(
            flex: 1,
            child: ListView.separated(
              controller: _thumbController,
              primary: false,
              itemBuilder: (context, index) {
                return Padding(
                  padding: const EdgeInsets.all(15),
                  child: SizedBox(
                    height: 150,
                    child: CustomPage(index: index),
                  ),
                );
              },
              separatorBuilder: (context, index) => const SizedBox(height: 3),
              itemCount: 5,
            ),
          ),
          // 主视图
          Expanded(
            flex: 9,
            child: ListView.separated(
              controller: _mainController,
              primary: false,
              itemBuilder: (context, index) {
                return Padding(
                  padding: const EdgeInsets.all(15),
                  child: SizedBox(
                    height: 850,
                    child: CustomPage(index: index),
                  ),
                );
              },
              separatorBuilder: (context, index) => const SizedBox(height: 3),
              itemCount: 5,
            ),
          ),
        ],
      ),
    );
  }
}

// 示例CustomPage组件,可根据自己的业务修改
class CustomPage extends StatelessWidget {
  final int index;
  const CustomPage({Key? key, required this.index}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.primaries[index % Colors.primaries.length].shade100,
      child: Center(child: Text('第$index页')),
    );
  }
}

可选优化

如果需要更精准的按索引对齐效果,可以在滚动监听中判断当前可见的第一个索引,直接滚动到对应索引的位置即可,比按比例计算的对齐效果更贴合记事本的使用场景。

内容的提问来源于stack exchange,提问作者Anas-Qasem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 18:00:04