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

Flutter中带centerKey的SliverList上方Expandable组件向上展开异常

解决Sliver List中Expandable组件向上展开并滚动至顶部的问题

问题分析

在带有centerKey的CustomScrollView中,centerKey对应的Sliver是布局锚点,其上方的Sliver属于反向滚动区域(布局从锚点向上延伸)。此时使用expandable包的Expandable组件会出现两个问题:

  1. 组件展开方向向上,而非预期的向下
  2. 组件展开/收起时,页面会自动滚动至顶部,无法保持原位置

这是因为反向区域的布局坐标系与正常区域相反,且组件高度变化时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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 12:20:40