无状态组件中ScrollablePositionedList结合Navigator跳转致应用冻结的解决
问题场景
原本使用ListView配合Navigator的push/pop浏览文档章节,为实现页面初始滚动到特定项,替换为ScrollablePositionedList(SPL)后,第一个路由页面正常,但调用Navigator.push时应用冻结且无异常抛出。
问题原因
代码中使用了全局唯一的ItemScrollController,但每个ScrollablePositionedList实例需要独立的控制器。当push新路由创建第二个SPL时,两个SPL共用同一个控制器,导致内部状态冲突,引发应用冻结。
解决方案
- 移除全局的ItemScrollController,为每个MyScroll组件创建独立的控制器实例
- 若需手动触发滚动操作,可通过组件传递控制器的方式实现
修正后的代码
import 'package:flutter/material.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; List<String> data = [for(var i=0; i<100; i++) '-- Item $i --']; void main() => runApp(MaterialApp(title: 'Test', home: HomePage())); class HomePage extends StatelessWidget { const HomePage({super.key}); // 为首页的SPL创建独立控制器 final ItemScrollController _homeScrollController = ItemScrollController(); @override Widget build(BuildContext context) { var listAll = [for(var i=0; i<data.length; i++) i]; // data数组的索引列表 var listPartim = [for(var i=20; i<40; i++) i]; // data数组的部分索引列表 int hot = 0; // 需要高亮的项索引 return Scaffold( appBar: AppBar( title: Row(children: [ TextButton( onPressed: () => _homeScrollController.scrollTo( index: 20, duration: const Duration(seconds: 1), curve: Curves.easeInOutCubic ), child: const Text('滚动到第20项(仅ScrollablePositionedList可用)') ), TextButton( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => SecondPage(list: listPartim, hot: hot, title: '第二页')) ); }, child: const Text('跳转到第二页') ), ],), ), body: MyScroll(listAll, 25, scrollController: _homeScrollController), // 初始滚动到第25项 ); } } class SecondPage extends StatelessWidget { final List<int> list; // data数组的索引列表 final int hot; // 需要高亮的项索引 final String title; const SecondPage({super.key, required this.list, required this.hot, required this.title}); @override Widget build(BuildContext context) { // 为第二页的SPL创建独立控制器 final ItemScrollController _pageScrollController = ItemScrollController(); return Scaffold( appBar: AppBar(title: Text(title),), body: MyScroll(list, hot, scrollController: _pageScrollController), ); } } class MyScroll extends StatelessWidget { final List<int> list; final int hotItem; final ItemScrollController scrollController; const MyScroll(this.list, this.hotItem, {super.key, required this.scrollController}); @override Widget build(BuildContext context) => ScrollablePositionedList.separated( itemCount: list.length, initialScrollIndex: hotItem, itemBuilder: (context, index) => MyItem(list[index], hotItem), // 使用list中的真实data索引,修正原逻辑错误 itemScrollController: scrollController, separatorBuilder: (context, int index) => const Divider(height: 12, thickness: 1), ); } class MyItem extends StatelessWidget { final int i, hot; const MyItem(this.i, this.hot, {super.key}); @override Widget build(BuildContext context) => Center( heightFactor: 3, child: Container( height: 20.0, color: (i == hot) ? Colors.green : Colors.white, child: Text(data[i]) ) ); }
注:额外修正了MyItem的索引逻辑——原代码直接将SPL的本地索引传给MyItem,导致第二页高亮逻辑错误,现在改为传递list中的真实data索引,保证高亮正确。
内容的提问来源于stack exchange,提问作者Piet M
相关产品推荐
相关产品推荐

