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

Flutter中如何让PageView内的两个ListView Builder同步滚动

Flutter实现PageView内两个ListView的同步滚动

要实现PageView中两个ListView的同步滚动,核心是通过ScrollController监听滚动事件并同步两个列表的滚动位置,以下是具体实现方案:

实现步骤

  • 创建两个独立的ScrollController,分别绑定到两个ListView
  • 为两个控制器添加滚动监听,在回调中同步对方的滚动位置
  • 使用标记变量避免循环触发滚动事件
  • 在组件销毁时释放控制器资源

完整代码示例

import 'package:flutter/material.dart';

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

  @override
  State<SyncScrollPage> createState() => _SyncScrollPageState();
}

class _SyncScrollPageState extends State<SyncScrollPage> {
  late final ScrollController _listController1;
  late final ScrollController _listController2;
  bool _isSyncing = false; // 标记是否正在同步,避免循环触发

  @override
  void initState() {
    super.initState();
    _listController1 = ScrollController();
    _listController2 = ScrollController();

    // 为两个控制器添加滚动监听
    _listController1.addListener(_syncScroll);
    _listController2.addListener(_syncScroll);
  }

  void _syncScroll() {
    if (_isSyncing) return; // 正在同步时直接返回,阻断循环
    _isSyncing = true;

    // 判断当前触发滚动的是哪个控制器
    final activeController = _listController1.position.isScrollingNotifier.value
        ? _listController1
        : _listController2;
    final targetController = activeController == _listController1
        ? _listController2
        : _listController1;

    // 同步滚动位置,处理列表高度不一致的情况,避免超出滚动范围
    if (targetController.hasClients) {
      final maxScroll = targetController.position.maxScrollExtent;
      final targetOffset = activeController.offset.clamp(0.0, maxScroll);
      if (targetController.offset != targetOffset) {
        // 直接跳转滚动位置,如需平滑滚动可替换为animateTo
        targetController.jumpTo(targetOffset);
        // 平滑滚动示例:
        // targetController.animateTo(
        //   targetOffset,
        //   duration: const Duration(milliseconds: 100),
        //   curve: Curves.easeOut,
        // );
      }
    }

    // 延迟重置同步标记,确保同步完成后再响应下一次滚动
    Future.delayed(const Duration(milliseconds: 50), () {
      _isSyncing = false;
    });
  }

  @override
  void dispose() {
    // 销毁控制器,释放资源
    _listController1.dispose();
    _listController2.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('同步滚动示例')),
      body: PageView(
        children: [
          _buildListView(_listController1),
          _buildListView(_listController2),
        ],
      ),
    );
  }

  // 构建ListView的通用方法
  Widget _buildListView(ScrollController controller) {
    return ListView.builder(
      controller: controller,
      itemCount: 50,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text('列表项 $index'),
          leading: const Icon(Icons.list),
        );
      },
    );
  }
}

关键细节说明

  • 循环触发避免:通过_isSyncing标记,在同步过程中忽略新的滚动监听事件,防止两个控制器互相触发导致的循环问题
  • 滚动范围处理:使用clamp方法限制滚动位置,避免当两个ListView内容高度不同时,出现滚动超出边界的情况
  • 平滑滚动选项:如果需要更自然的同步效果,可将jumpTo替换为animateTo,并设置合适的动画时长和曲线
  • 资源释放:必须在dispose方法中销毁ScrollController,避免内存泄漏

内容的提问来源于stack exchange,提问作者Immanuvel Blessanraj Mariappan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 18:54:21