Flutter中如何让两个SingleChildScrollView同步垂直滚动?
Flutter实现两个SingleChildScrollView同步垂直滚动
要实现两个SingleChildScrollView的同步垂直滚动,核心是通过ScrollController监听滚动事件,同步两个视图的滚动偏移,以下是具体实现方案:
基础同步(内容高度一致)
如果两个滚动视图的内容总高度相同,可直接同步滚动偏移:
class SyncScrollDemo extends StatefulWidget { const SyncScrollDemo({super.key}); @override State<SyncScrollDemo> createState() => _SyncScrollDemoState(); } class _SyncScrollDemoState extends State<SyncScrollDemo> { late ScrollController _leftController; late ScrollController _rightController; bool _isSyncing = false; // 避免循环触发滚动监听 @override void initState() { super.initState(); _leftController = ScrollController(); _rightController = ScrollController(); // 监听两侧滚动事件,触发同步逻辑 _leftController.addListener(_syncScroll); _rightController.addListener(_syncScroll); } void _syncScroll() { if (_isSyncing) return; _isSyncing = true; final currentController = WidgetsBinding.instance.focusedScrollController; if (currentController == _leftController) { _rightController.jumpTo(_leftController.offset); } else { _leftController.jumpTo(_rightController.offset); } // 延迟重置标记,防止滚动卡顿 Future.delayed(const Duration(milliseconds: 10), () { _isSyncing = false; }); } @override void dispose() { _leftController.dispose(); _rightController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('同步滚动示例')), body: Row( children: [ // 左侧滚动视图 Expanded( child: SingleChildScrollView( controller: _leftController, child: Column( children: List.generate(50, (i) => ListTile(title: Text('左侧条目 $i'))), ), ), ), // 右侧滚动视图 Expanded( child: SingleChildScrollView( controller: _rightController, child: Column( children: List.generate(50, (i) => ListTile(title: Text('右侧条目 $i'))), ), ), ), ], ), ); } }
适配内容高度不一致的情况
如果两个视图的内容总高度不同,需按滚动比例计算偏移量,保证滚动进度同步:
修改_syncScroll方法为:
void _syncScroll() { if (_isSyncing) return; _isSyncing = true; final leftMax = _leftController.position.maxScrollExtent; final rightMax = _rightController.position.maxScrollExtent; if (WidgetsBinding.instance.focusedScrollController == _leftController) { // 左侧滚动,按比例计算右侧偏移 final ratio = rightMax / leftMax; _rightController.jumpTo(_leftController.offset * ratio); } else { // 右侧滚动,按比例计算左侧偏移 final ratio = leftMax / rightMax; _leftController.jumpTo(_rightController.offset * ratio); } Future.delayed(const Duration(milliseconds: 10), () { _isSyncing = false; }); }
注意事项
_isSyncing标记用于避免循环触发滚动监听,防止卡顿或无限循环- 若需要平滑滚动,可将
jumpTo替换为animateTo,并设置动画时长和曲线参数 - 组件销毁时必须调用
dispose()释放ScrollController资源
内容的提问来源于stack exchange,提问作者erkaneroglu
相关产品推荐
相关产品推荐

