Flutter中带centerKey的SliverList上方Expandable组件向上展开异常
解决Sliver List中Expandable组件向上展开并滚动至顶部的问题
问题分析
在带有centerKey的CustomScrollView中,centerKey对应的Sliver是布局锚点,其上方的Sliver属于反向滚动区域(布局从锚点向上延伸)。此时使用expandable包的Expandable组件会出现两个问题:
- 组件展开方向向上,而非预期的向下
- 组件展开/收起时,页面会自动滚动至顶部,无法保持原位置
这是因为反向区域的布局坐标系与正常区域相反,且组件高度变化时CustomScrollView会自动调整滚动位置以维持锚点可见性。
解决方案
1. 改造Expandable组件,控制展开方向
修改ExpandedItem组件,通过参数标记是否处于顶部Sliver,调整布局结构让展开内容始终出现在触发项的视觉下方,同时加入滚动位置保存逻辑:
class ExpandedItem extends StatefulWidget { final bool isInTopSliver; final ScrollController scrollController; const ExpandedItem({ Key? key, this.isInTopSliver = false, required this.scrollController, }) : super(key: key); @override State<ExpandedItem> createState() => _ExpandedItemState(); } class _ExpandedItemState extends State<ExpandedItem> { final controller = ExpandableController(); List<Widget> buildItems() { final items = <Widget>[]; for (int i = 0; i < 10; i++) { items.add(GestureDetector( onTap: i == 9 ? () { final scrollPos = widget.scrollController.offset; controller.toggle(); WidgetsBinding.instance.addPostFrameCallback((_) { widget.scrollController.jumpTo(scrollPos); }); } : null, child: Container( width: double.infinity, height: 100, color: (i % 2 == 0) ? Colors.orange : Colors.brown, ), )); } return items; } @override Widget build(BuildContext context) { final expandedContent = [ Container( color: Colors.yellow, width: double.infinity, height: 100, ), ...buildItems(), ]; // 顶部Sliver中反转布局顺序,确保视觉上展开内容在下方 if (widget.isInTopSliver) { expandedContent.reverse(); } return ExpandableNotifier( controller: controller, child: Expandable( collapsed: GestureDetector( onTap: () { final scrollPos = widget.scrollController.offset; controller.toggle(); WidgetsBinding.instance.addPostFrameCallback((_) { widget.scrollController.jumpTo(scrollPos); }); }, child: Container( color: Colors.yellow, width: double.infinity, height: 100, ), ), expanded: Column( mainAxisSize: MainAxisSize.min, reverse: widget.isInTopSliver, children: expandedContent, ), ), ); } }
2. 调整调用逻辑
给CustomScrollView绑定ScrollController,并在构建子项时给顶部Sliver的Expandable传入对应参数:
// 定义控制器和锚点Key final _scrollController = ScrollController(); final _centerKey = GlobalKey(); // 构建CustomScrollView CustomScrollView( controller: _scrollController, center: _centerKey, slivers: [ SliverList( delegate: SliverChildListDelegate( buildItems(1), ), ), SliverList( key: _centerKey, delegate: SliverChildListDelegate( buildItems(2), ), ), SliverList( delegate: SliverChildListDelegate( buildItems(3), ), ), ], ); // 修改buildItems方法,传递参数 List<Widget> buildItems(int listIndex) { final items = <Widget>[]; for (int i = 0; i < 10; i++) { if (i == 5) { items.add(ExpandedItem( isInTopSliver: listIndex == 1, scrollController: _scrollController, )); } else { items.add(Container( width: double.infinity, height: 200, color: (i % 2 == 0) ? Colors.blue : Colors.red, )); } } return items; }
效果说明
- 顶部Sliver中的Expandable点击后,橙色/棕色的展开内容会出现在黄色触发项的视觉下方
- 展开/收起操作不会导致页面滚动至顶部,保持当前浏览位置
内容的提问来源于stack exchange,提问作者Martin_Crsk
相关产品推荐
相关产品推荐

