Flutter StickyGroupedListView滚动到指定索引回弹至顶部问题
问题背景
开发中使用sticky_grouped_list组件实现分组列表,该组件包含scrollable_positioned_list组件的全部滚动控制能力。
需求为:选中日历日期后,列表自动滚动到对应日期的索引位置。目前逻辑编写完成后出现异常:选中日期触发滚动时,列表滚动到目标索引后会立刻回弹到顶部,无法完成定位。
- 异常效果演示:

- 期望效果(列表底部预留足够滚动空间,定位不回弹):

当前实现代码
class PlanificationPage extends StatefulWidget { const PlanificationPage({Key? key}) : super(key: key); @override State<PlanificationPage> createState() => _PlanificationPageState(); } class _PlanificationPageState extends State<PlanificationPage> { final RefreshController refreshListController = RefreshController(initialRefresh: false); final GroupedItemScrollController itemScrollController = GroupedItemScrollController(); final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create(); @override void initState() { super.initState(); } moveToIndex(index) { if (itemScrollController.isAttached) { itemScrollController.scrollTo( index: index, duration: const Duration(milliseconds: 500)); } } @override Widget build(BuildContext context) { final adminBloc = BlocProvider.of<AdminBloc>(context); return Scaffold( appBar: AppBar( centerTitle: true, title: Text( AppStrings.whiteboardProgrammingTitle, style: UiHelper.appBartextStyle, ), ), body: Column( children: [ Container( padding: const EdgeInsets.only(top: 10), color: Theme.of(context).colorScheme.surface, child: BlocBuilder<AdminBloc, AdminState>( builder: (context, state) { return TableCalendar( firstDay: DateTime.utc(2010, 10, 16), lastDay: DateTime.utc(2030, 3, 14), focusedDay: state.focusedDay!, calendarFormat: CalendarFormat.week, startingDayOfWeek: StartingDayOfWeek.monday, headerVisible: false, selectedDayPredicate: (DateTime day) { return isSameDay(state.selectedDay, day); }, onDaySelected: (DateTime selectedDay, DateTime focusedDay) { if (!isSameDay(state.selectedDay, selectedDay)) { adminBloc.add(OnSelectedDay( selectedDay: selectedDay, focusedDay: focusedDay, )); moveToIndex(selectedDay.weekday - 1); } }, onPageChanged: (DateTime focusedDay) { adminBloc ..add(OnPageCalendarChangedEvent(focusedDay)) ..add(OnLoadWorkoutsEvent()); }, onCalendarCreated: (_) { adminBloc.add(OnLoadWorkoutsEvent()); }, ); }, ), ), BlocBuilder<AdminBloc, AdminState>( builder: (context, state) { if (state.loadingList) { return const Center( child: CircularProgressIndicator(), ); } final List<WorkoutsByDatesModel> workouts = state.listWorkouts; return Expanded( child: SmartRefresher( controller: refreshListController, enablePullDown: true, onRefresh: () { BlocProvider.of<AdminBloc>(context) .add(OnLoadWorkoutsEvent()); refreshListController.refreshCompleted(); }, child: SingleChildScrollView( child: StickyGroupedListView<WorkoutsByDatesModel, String>( shrinkWrap: true, elements: workouts, // 已尝试添加底部500像素padding,问题依旧存在 //padding: const EdgeInsets.only(bottom: 500.0), // 已尝试调整physics为AlwaysScrollableScrollPhysics,无效 //physics: const AlwaysScrollableScrollPhysics(), itemScrollController: itemScrollController, groupBy: (WorkoutsByDatesModel element) => element.date.weekday.toString(), groupComparator: (value1, value2) => value1.compareTo(value2), groupSeparatorBuilder: (WorkoutsByDatesModel value) => Padding( padding: const EdgeInsets.only(top: 10.0, bottom: 10), child: Center( child: Text( AppUtils.getDayName(value.date.weekday), style: const TextStyle( fontSize: 12, ), ), ), ), indexedItemBuilder: (BuildContext context, WorkoutsByDatesModel element, int index) { final workouts = element.workouts; return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: workouts.length + 1, itemBuilder: (BuildContext context, int i) { if (i == workouts.length) { return GestureDetector( onTap: () { context .read<AdminBloc>() .add(OnSelectedCardDay(element.date)); context.router.push( const WorkoutTypeSelectionRouter()); }, child: Container( color: Theme.of(context).colorScheme.surface, padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 10), child: Row( children: [ const Icon( Icons.add_circle_rounded, size: 16, ), const SizedBox(width: 5), Text( AppStrings.addNewWorkout, style: const TextStyle(fontSize: 16), ) ], ), ), ); } else { return Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), children: [ SlidableAction( onPressed: (context) { // 待实现:跳转编辑训练页面 print('edit'); }, backgroundColor: Colors.blue, foregroundColor: Colors.white, label: AppStrings.editButton, ) ], ), child: Container( color: Theme.of(context).colorScheme.surface, padding: const EdgeInsets.symmetric( horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(4.0), child: Text( AppStrings.workoutOfDayTitle, style: TextStyle( fontSize: 10, color: Theme.of(context) .disabledColor)), ), Padding( padding: const EdgeInsets.only( left: 8.0, bottom: 10), child: Text( workouts[i].title, style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 16, ), ), ), Container( height: 0.5, width: double.infinity, color: Theme.of(context).dividerColor, ), ], ), ), ); } }, ); }, ), ), ), ); }, ), ], )); } }
已尝试无效方案
- 给列表添加底部padding
- 修改列表的physics滚动属性
诉求
排查当前实现的错误点,给出可落地的解决方案,实现列表底部预留足够滚动空间、滚动到指定索引不回弹的正常效果。
内容的提问来源于stack exchange,提问作者Jean Cuervo
相关产品推荐
相关产品推荐

