Flutter实现SingleChildScrollView底部联动DraggableScrollableSheet
Flutter:实现SingleChildScrollView底部联动DraggableScrollableSheet
需求说明
我需要在Flutter中实现以下效果:当SingleChildScrollView滚动至底部时,DraggableScrollableSheet恰好贴合在其底部,且可通过顶部手柄拖动调整高度,拖动过程中Sheet内部内容能正常滚动。
当前实现代码
class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Where are you going today?'), ), body: Stack( children: [ SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(16.0), child: Text( 'Hi, Vidhan!', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( 'Your location', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), ), ), Padding( padding: const EdgeInsets.all(16.0), child: Container( height: 200, color: Colors.grey[300], child: Center( child: Text('Map Placeholder'), ), ), ), SizedBox(height: 200), // 占位内容,用于展示滚动效果 ], ), ), DraggableScrollableSheet( initialChildSize: 0.3, minChildSize: 0.1, maxChildSize: 0.7, builder: (BuildContext context, ScrollController scrollController) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 10, spreadRadius: 5, ), ], ), child: Column( children: [ Container( margin: EdgeInsets.symmetric(vertical: 10), width: 40, height: 5, decoration: BoxDecoration( color: Colors.grey, borderRadius: BorderRadius.circular(5), ), ), Expanded( child: ListView( controller: scrollController, children: [ ListTile( title: Text('Where to?'), trailing: Text('Manage'), ), GridView.count( padding: EdgeInsets.all(16), shrinkWrap: true, crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, children: [ LocationCard(title: 'Home', location: 'Mathura, India'), LocationCard(title: 'Office', location: 'Mumbai, India'), LocationCard(title: 'Salon', location: 'Mathura, India'), LocationCard(title: "Friend's Home", location: 'Agra, India'), ], ), ], ), ), ], ), ); }, ), ], ), ); } }
解决方案
当前Stack布局会让DraggableScrollableSheet始终悬浮在SingleChildScrollView上方,无法实现“滚动到底部才贴合”的联动效果。推荐使用NestedScrollView重构布局,实现自然的滚动衔接:
class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Where are you going today?'), ), body: NestedScrollView( // 顶部滚动内容 headerSliverBuilder: (context, innerBoxIsScrolled) => [ SliverList( delegate: SliverChildListDelegate([ Padding( padding: const EdgeInsets.all(16.0), child: Text( 'Hi, Vidhan!', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( 'Your location', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), ), ), Padding( padding: const EdgeInsets.all(16.0), child: Container( height: 200, color: Colors.grey[300], child: Center( child: Text('Map Placeholder'), ), ), ), SizedBox(height: 200), // 占位内容 ]), ), ], // 底部可拖动Sheet body: DraggableScrollableSheet( initialChildSize: 0.3, minChildSize: 0.1, maxChildSize: 0.7, builder: (BuildContext context, ScrollController scrollController) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 10, spreadRadius: 5, ), ], ), child: Column( children: [ // 拖动手柄 Container( margin: EdgeInsets.symmetric(vertical: 10), width: 40, height: 5, decoration: BoxDecoration( color: Colors.grey, borderRadius: BorderRadius.circular(5), ), ), Expanded( child: ListView( controller: scrollController, children: [ ListTile( title: Text('Where to?'), trailing: Text('Manage'), ), GridView.count( padding: EdgeInsets.all(16), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), // 禁用自身滚动,避免和外层控制器冲突 crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, children: [ LocationCard(title: 'Home', location: 'Mathura, India'), LocationCard(title: 'Office', location: 'Mumbai, India'), LocationCard(title: 'Salon', location: 'Mathura, India'), LocationCard(title: "Friend's Home", location: 'Agra, India'), ], ), ], ), ), ], ), ); }, ), ), ); } } // 补充LocationCard实现 class LocationCard extends StatelessWidget { final String title; final String location; const LocationCard({required this.title, required this.location, Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), SizedBox(height: 4), Text(location, style: TextStyle(fontSize: 12, color: Colors.grey[600])), ], ), ); } }
关键修改点
- 用
NestedScrollView替代Stack,让顶部内容作为可滚动的Sliver组件,底部Sheet作为body,实现滚动到底部时自然衔接; - 给内部
GridView添加NeverScrollableScrollPhysics(),避免和DraggableScrollableSheet的滚动控制器产生冲突; - 补充了
LocationCard的完整实现,修复原代码中的字符串引号错误。
内容的提问来源于stack exchange,提问作者Asesha
相关产品推荐
相关产品推荐

