GetX中ListView.builder如何实现正常工作的RefreshIndicator
问题根源
你遇到的矛盾本质不是physics属性选哪个值的问题,而是嵌套了两层可滚动组件导致手势冲突:外层是SingleChildScrollView,内层是开了shrinkWrap: true的ListView.builder,两种physics配置的问题分别是:
- 给ListView设
AlwaysScrollableScrollPhysics()时,ListView会优先抢占滚动事件,但因为开了shrinkWrap,ListView高度和内容总高度一致,自身没有可滚动的视口空间,所以本身滚不动,只有外层的RefreshIndicator能响应下拉 - 给ListView设
NeverScrollableScrollPhysics()时,ListView完全不处理滚动,所有滚动事件都交给外层SingleChildScrollView,但RefreshIndicator是包在ListView外面的,拿不到下拉的滚动手势,自然触发不了刷新
正确修复方案
去掉外层多余的SingleChildScrollView,不要用shrinkWrap: true强行撑满ListView高度,直接把RefreshIndicator放在Expanded包裹的层级,ListView不需要手动修改physics属性,用默认值即可。
修改后的核心代码如下:
return Scaffold( body: SafeArea( child: Padding( padding: EdgeInsets.all(10), child: Column( children: [ FirstRow( headingText: 'My Appointments', context: context, ), SizedBox(height: 10), Obx(() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppButtonLarge( selected: appointmentController.appointmentT == 'upcomingAppointments' ?? false, text: 'Upcoming', onTap: onAppointmentChange), AppButtonLarge( selected: appointmentController.appointmentT == 'pastAppointments' ?? false, text: 'Past', onTap: onAppointmentChange), ], ); }), SizedBox(height: 10), // 移除外层冗余的SingleChildScrollView,直接在Expanded下放置内容 Expanded( child: GetX<DoctorAppointmentController>( builder: (controller) { print(controller.appointmentList.length); if (controller.isLoading.value) { return AppWidgetsCard.getProgressIndicator(); } if (controller.appointmentList.length == 0) { return AppWidgetsCard.getEmptyCard('Appointment'); } return RefreshIndicator( key: refreshKey, onRefresh: () async { await Navigator.pushReplacement( context, PageRouteBuilder( pageBuilder: (a,b,c)=>GetAllDrAppointments(), transitionDuration: Duration(seconds: 3) ) ); }, // ListView直接作为RefreshIndicator的子节点,不需要shrinkWrap,不需要手动改physics child: Obx(()=>ListView.builder( itemCount: controller.appointmentList.length, scrollDirection: Axis.vertical, itemBuilder: (context, index) { void goToNext() { Navigator.push( context, MaterialPageRoute( builder: (context) => MyAppointments( appointment: controller.appointmentList[index], isDoctor: true ), )); } if (index == controller.appointmentList.length - 1) { id = controller.appointmentList[index].id; return Column( children: [ TextButton( onPressed: goToNext, child: AppWidgetsCard.getAppointmentCard( controller.appointmentList[index], goToNext, isDoctor: true ), ), Obx(() { return controller.loadMore.value ? AppWidgetsCard.getProgressIndicator() : Container( height: 40, width: 200, child: AppMethodButton( selected: true, text: 'Load More', onTapMethod: () { print('data'); controller.fetchMoreAppointments(id); } ), ); }) ], ); } else { return TextButton( onPressed: goToNext, child: AppWidgetsCard.getAppointmentCard( controller.appointmentList[index], goToNext, isDoctor: true ) ); } }, ), ), ); }, ), ) ], ), )), bottomNavigationBar: Container( height: 50, child: LastRow( page: 'Appointment', )), );
关键注意点
- 不要为了适配Column布局给ListView开
shrinkWrap: true再套外层SingleChildScrollView,这种写法会一次性加载所有ListView子项,性能差,还会直接导致滚动事件冲突 RefreshIndicator必须直接作为可滚动组件的最外层父节点,保证能第一时间接收到下拉手势,不能被其他父级可滚动组件抢占事件- 常规场景下ListView不需要手动设置physics,默认配置会自动适配平台滚动逻辑、响应下拉刷新手势,只有需要禁止滚动、和父组件做特殊滚动联动时才需要手动修改physics值
内容的提问来源于stack exchange,提问作者Ijaz Ahmed
相关产品推荐
相关产品推荐

