Flutter Bloc屏幕旋转时LinkedScrollPosition控制器变更报错求助
解决Flutter横竖联动滚动组件屏幕旋转报错问题
问题场景
实现了横竖联动的时间表组件:横向表头为日期(Day),纵向表头为时段(Period),使用linked_scroll_controller实现滚动联动,Flutter Bloc做状态管理。组件正常运行,但屏幕旋转时抛出如下错误:
======== Exception caught by widgets library ======================================================= The following assertion was thrown building SingleChildScrollView(dependencies: [Directionality]): _LinkedScrollPosition cannot change controllers once created. 'package:linked_scroll_controller/linked_scroll_controller.dart': Failed assertion: line 142 pos 12: 'linkedPosition.owner == this' The relevant error-causing widget was: SingleChildScrollView SingleChildScrollView: When the exception was thrown, this was the stack: #2 _LinkedScrollController.attach (package:linked_scroll_controller/linked_scroll_controller.dart:142:12) #3 ScrollableState.didUpdateWidget (package:flutter/src/widgets/scrollable.dart:561:34) #4 StatefulElement.update (package:flutter/src/widgets/framework.dart:5142:55) #5 Element.updateChild (package:flutter/src/widgets/framework.dart:3660:15)
问题根源
- 控制器重复创建:在
build方法中每次都会新建LinkedScrollControllerGroup和对应的ScrollController实例。屏幕旋转触发Widget重建时,旧控制器仍与SingleChildScrollView绑定,新控制器尝试关联同一滚动位置时触发断言冲突。 - 重复发起Bloc事件:每次
build都调用context.read<ClassBloc>().add(FetchPeriodTimeTable(widget.classId)),导致不必要的重复请求。
修复步骤
- 在
initState中初始化滚动控制器,确保只创建一次 - 在
dispose中销毁控制器,释放资源 - 将Bloc事件移至
initState,仅在组件初始化时发起请求;若需响应widget.selClass变化,在didUpdateWidget中处理 - 重建Widget时复用已有的控制器实例
修改后的代码
MyTimeTable组件代码
import 'package:linked_scroll_controller/linked_scroll_controller.dart'; import '../../time_table.dart'; class MyTimeTable extends StatefulWidget { final SchoolClass selClass; MyTimeTable({Key? key, required this.selClass}) : super(key: key); @override State<MyTimeTable> createState() => _MyTimeTableState(); } class _MyTimeTableState extends State<MyTimeTable> { late LinkedScrollControllerGroup _horizontalControllersGroup; late ScrollController _horizontalBody; late ScrollController _horizontalHeader; late LinkedScrollControllerGroup _verticalControllersGroup; late ScrollController _verticalBody; late ScrollController _verticalHeader; List<dynamic>? periodList; List<dynamic>? timeTableList; final List<String> rowEntries = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday",]; @override void initState() { super.initState(); // 初始化滚动控制器,仅执行一次 _horizontalControllersGroup = LinkedScrollControllerGroup(); _horizontalBody = _horizontalControllersGroup.addAndGet(); _horizontalHeader = _horizontalControllersGroup.addAndGet(); _verticalControllersGroup = LinkedScrollControllerGroup(); _verticalBody = _verticalControllersGroup.addAndGet(); _verticalHeader = _verticalControllersGroup.addAndGet(); // 仅初始化时发起一次数据请求 context.read<ClassBloc>().add(FetchPeriodTimeTable(widget.selClass.classId)); } @override void didUpdateWidget(covariant MyTimeTable oldWidget) { super.didUpdateWidget(oldWidget); // 当选中的班级变化时,重新请求数据 if (oldWidget.selClass.classId != widget.selClass.classId) { context.read<ClassBloc>().add(FetchPeriodTimeTable(widget.selClass.classId)); } } @override void dispose() { // 销毁控制器,避免内存泄漏 _horizontalBody.dispose(); _horizontalHeader.dispose(); _verticalBody.dispose(); _verticalHeader.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( resizeToAvoidBottomInset: false, appBar: sizedAppBar, body: SafeArea( child: Container( child: Column( children: [ Row( children: <Widget>[ SizedBox(height: cellHeight,width: cellWidth,...), Expanded( child: Container(// horizontal day header height: 65, width: 400, child: SingleChildScrollView( scrollDirection: Axis.horizontal, controller: _horizontalHeader, child: HeaderContainer(rowEntries: rowEntries), ), ), ) ], ), Expanded( child: Container( child: Row( children: <Widget>[ Container(// vertical period header width: 100, child: SingleChildScrollView( controller: _verticalHeader, child: getColumnContainer(), ), ), Expanded( // data grid child: SingleChildScrollView( controller: _verticalBody, child: SingleChildScrollView( scrollDirection: Axis.horizontal, controller: _horizontalBody, child: getBodyContainer(), ), ), ) ], ), ), ), ], ), ), ), ), ); } getColumnContainer() { // vertical period header return BlocBuilder<ClassBloc, ClassState>( builder: (context, state) { if(state is PeriodTimeTableList) { periodList = state.item1MultiList; } if(periodList != null) { int numberOfRows = periodList!.length; return ColumnContainer( colEntries: periodList!, ); } return const SizedBox(); }, ); } getBodyContainer() { return BlocBuilder<ClassBloc, ClassState>( builder: (context, state) { if(state is PeriodTimeTableList) { periodList = state.item1MultiList; timeTableList = state.item2MultiList; } if(periodList != null && timeTableList != null) { int _numberOfColumns = rowEntries.length; int _numberOfLines = periodList!.length; int itemCounter = 0; return BodyContainer( rowEntries: rowEntries, colEntries: periodList!, dataList: timeTableList!, ); } return SizedBox(); }, ); } }
ClassBloc代码(无需修改,保留原逻辑)
class ClassBloc extends Bloc<ClassEvent, ClassState> { final MyApi myApi; ClassBloc({required this.myApi}) : super(ClassInitial()) { on<FetchPeriodTimeTable>((event, emit) async { var result = await myApi.getPeriodTimeTable(event.classId); List<dynamic> periodList = []; periodList = result['periodList']; List<dynamic> timeTableList = []; timeTableList = result['timeTableList']; emit(PeriodTimeTableList(item1MultiList: periodList,item2MultiList: timeTableList)); }); } Future<void> updateTimeTable(TimeTable timeTable) async { try { // to save add/edit function and update the list var result = await myApi.updateTimeTable(timeTable); if(result['status'] == 1) { List<dynamic> periodList = []; periodList = result['periodList']; List<dynamic> timeTableList = []; timeTableList = result['timeTableList']; emit(const TimeTableCreated()); emit(PeriodTimeTableList(item1MultiList: periodList,item2MultiList: timeTableList)); } else if(result['index'] == 2) { emit(const TimeTableNotCreated()); } else { emit(const ErrorDialog(title: 'Sorry',message: 'Something went wrong')); } } catch (error) { emit(const ErrorDialog(title: 'Sorry',message: 'Something went wrong')); } } }
内容的提问来源于stack exchange,提问作者KJEjava48
相关产品推荐
相关产品推荐

