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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 23:30:39