Flutter如何实现拖拽手柄触发DraggableScrollableSheet上滑(仿谷歌地图)
Flutter DraggableScrollableSheet固定拖拽手柄实现方案
下面提供两种可直接运行的实现方案,都能做到手柄固定不随列表滚动、触摸手柄可正常触发Sheet拖拽上滑/下滑。
方案1:SliverPersistentHeader 实现(推荐,无手势冲突)
这是最稳定的实现方式,不需要手动处理手势转发,完全依托Flutter原生滚动体系实现:
- 用
CustomScrollView作为DraggableScrollableSheet的子组件 - 将拖拽手柄封装为
pinned: true的SliverPersistentHeader,滚动时会自动固定在列表顶部,永远不会滑出可视区域 - 手柄本身属于ScrollView的子组件,触摸拖拽事件会自动被DraggableScrollableSheet识别,不需要额外转发
核心实现代码:
DraggableScrollableSheet( initialChildSize: 0.4, minChildSize: 0.2, maxChildSize: 0.9, snap: true, // 可选:开启松手自动吸附效果 builder: (context, scrollController) { // 必须使用builder回调提供的scrollController,不要自行创建 return CustomScrollView( controller: scrollController, slivers: [ // 固定在顶部的拖拽手柄 SliverPersistentHeader( pinned: true, // 核心属性:滚动时固定在顶部不消失 delegate: DragHandleDelegate(handleHeight: 40), ), // 列表内容区 SliverList( delegate: SliverChildBuilderDelegate( (context, index) => ListTile( title: Text('列表项 $index'), ), childCount: 50, // 任意长度列表都不会影响手柄显示 ), ) ], ); }, ) // 拖拽手柄的Sliver代理类 class DragHandleDelegate extends SliverPersistentHeaderDelegate { final double handleHeight; DragHandleDelegate({required this.handleHeight}); @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return Container( alignment: Alignment.topCenter, padding: const EdgeInsets.only(top: 12), child: Container( width: 40, height: 5, decoration: BoxDecoration( color: Colors.grey[400], borderRadius: BorderRadius.circular(3), ), ), ); } @override double get maxExtent => handleHeight; @override double get minExtent => handleHeight; @override bool shouldRebuild(covariant DragHandleDelegate oldDelegate) => false; }
方案2:Stack + 手势转发实现
如果需要手柄悬浮在列表内容上方(不占用列表布局空间),可以用Stack布局固定手柄位置,手动转发拖拽事件给Sheet的ScrollController:
DraggableScrollableSheet( initialChildSize: 0.4, minChildSize: 0.2, maxChildSize: 0.9, builder: (context, scrollController) { return Stack( children: [ // 列表区域,顶部预留手柄高度避免内容被遮挡 ListView.builder( controller: scrollController, padding: const EdgeInsets.only(top: 40), itemCount: 50, itemBuilder: (context, index) => ListTile(title: Text('列表项 $index')), ), // 固定在顶部的悬浮手柄 Positioned( top: 12, left: 0, right: 0, child: GestureDetector( behavior: HitTestBehavior.translucent, onVerticalDragUpdate: (details) { // 将手柄的拖拽增量同步给ScrollController,触发Sheet滚动 final currentOffset = scrollController.position.pixels; scrollController.jumpTo( (currentOffset - details.delta.dy) .clamp(0.0, scrollController.position.maxScrollExtent), ); }, child: Center( child: Container( width: 40, height: 5, decoration: BoxDecoration( color: Colors.grey[400], borderRadius: BorderRadius.circular(3), ), ), ), ), ) ], ); }, )
注意事项:不要自行创建ScrollController传给Sheet的子组件,必须使用builder方法回调提供的
scrollController实例,否则会出现手势不同步、Sheet无法响应拖拽的问题。
实现效果参考:
内容的提问来源于stack exchange,提问作者davyzhang
相关产品推荐
相关产品推荐

