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

如何实现BottomSheet Modal中可拖拽的固定Header?

解决方案

要实现即使列表处于过度滚动状态,拖拽固定Header也能收起BottomSheet的效果,可以通过给Header添加手势监听直接响应关闭操作,同时优化滚动组件的嵌套结构。

修复后的代码

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: ElevatedButton(
            child: const Text('Show modal'),
            onPressed: () => {
              showModalBottomSheet(
                  backgroundColor: Colors.transparent,
                  clipBehavior: Clip.antiAlias,
                  context: context,
                  enableDrag: true,
                  isDismissible: true,
                  isScrollControlled: true,
                  builder: (modalContext) => MyWidget())
            },
          ),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  // 触发关闭的拖拽距离阈值
  final _dragThreshold = 50.0;
  double _dragStartY = 0;

  @override
  Widget build(BuildContext context) {
    return DraggableScrollableSheet(
      initialChildSize: 0.5,
      minChildSize: 0.5,
      maxChildSize: 0.9,
      expand: false,
      builder: (BuildContext context, ScrollController scrollController) =>
          Container(
        color: Colors.white,
        child: CustomScrollView(
          controller: scrollController,
          slivers: [
            SliverAppBar(
              title: GestureDetector(
                // 记录拖拽起始位置
                onVerticalDragStart: (details) => _dragStartY = details.globalPosition.dy,
                // 监听拖拽更新,超过阈值则关闭BottomSheet
                onVerticalDragUpdate: (details) {
                  final dragDelta = details.globalPosition.dy - _dragStartY;
                  if (dragDelta > _dragThreshold) {
                    Navigator.pop(context);
                  }
                },
                child: const Text("Header Title"),
              ),
              pinned: true,
            ),
            // 替换原有的SliverFillRemaining+ListView,避免嵌套滚动冲突
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, index) {
                  return ListTile(
                      leading: const Icon(Icons.list),
                      trailing: const Text(
                        "GFG",
                        style: TextStyle(color: Colors.green, fontSize: 15),
                      ),
                      title: Text("List item $index"));
                },
                childCount: 500,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键修改说明

  1. 将MyWidget改为StatefulWidget:用于存储拖拽起始位置和关闭阈值,实现手势状态的跟踪。
  2. 给Header添加手势监听:在SliverAppBar的title上包裹GestureDetector,监听垂直拖拽事件。当向下拖拽距离超过设定的阈值时,直接调用Navigator.pop(context)关闭BottomSheet,绕过列表的过度滚动手势消费。
  3. 优化滚动组件结构:移除原有的SliverFillRemaining嵌套ListView的写法,改用SliverList直接生成列表项,避免嵌套滚动导致的手势冲突,同时更符合CustomScrollView的设计规范。

替代方案(保留过度滚动效果)

如果你希望保留列表的过度滚动弹性效果,同时实现拖拽Header关闭的功能,可以自定义ScrollBehavior,让手势在过度滚动时仍能传递给BottomSheet:

class NoOverScrollInterceptBehavior extends ScrollBehavior {
  @override
  Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
    return child;
  }

  @override
  ScrollPhysics getScrollPhysics(BuildContext context) {
    return const BouncingScrollPhysics();
  }
}

然后在CustomScrollView中设置该行为:

CustomScrollView(
  controller: scrollController,
  behavior: NoOverScrollInterceptBehavior(),
  slivers: [...],
)

内容的提问来源于stack exchange,提问作者Akinjiola Toni

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 11:45:45