如何实现可调整大小、滚动、滑动关闭的Flutter底部组件?
问题描述
我正在学习某YouTube视频里的设计模式,目前我的Flutter页面核心代码如下:
Widget build(BuildContext context) { super.build(context); RideServices rideServices = ride_services(context); return Scaffold( resizeToAvoidBottomInset: true, key: scaffoldKey, extendBodyBehindAppBar: true, body: SwipeDrawer( radius: 20, key: drawerKey, drawer: Theme( data: Theme.of(context).copyWith( canvasColor: Colors.blue[400], ), child: Drawer_widget(), ), hasClone: false, bodyBackgroundPeekSize: 30, backgroundColor: Colors.blue[400], child: Stack( children: [ GOOGLE_container(), RefreshWidget(), my_location(), BottomBar( mapController: osmController, saveRiderAndSearchNearRidecallback: (listDriver) async { }, ), drawer(drawerKey: drawerKey), SaveLocationCheckMark(key: UniqueKey(), saveLocation: saveLocation), detail_driver(), splash_screen() ], ), ), ); }
其中底部的BottomBar组件我用IndexedStack实现,代码如下:
@override Widget build(BuildContext context) { return Positioned( top: 67.5.h, bottom: 0, left: 0, right: 0, child: Selector<ProviderService, int>( selector: (ctx, prod) => prod.indexWidgetInformationPicker, shouldRebuild: (nPage, oldPage) => nPage != oldPage, builder: (ctx, indexPage, child) { return IndexedStack( index: indexPage, sizing: StackFit.passthrough, children: [ pickUpDestination( widget: widget, my_loc_controller: my_loc_controller), BestRouteCalculator( colorizeTextStyle: colorizeTextStyle, colorizeColors: colorizeColors), pickRide( widget: widget, my_loc_controller: my_loc_controller, ), FindADriver( key: UniqueKey(), colorizeTextStyle: colorizeTextStyle, colorizeColors: colorizeColors), RideStarted(), ], ); }, ), ); }
现在遇到两个问题:
- 我的
IndexedStack无法调整子组件大小(均为固定尺寸),想知道视频中使用的是什么组件? IndexedStack会同时构建所有子组件,但我需要按需显示/隐藏;另外想要实现视频中那种可调整大小、支持滚动/滑动、能关闭的底部弹窗效果,该怎么修改现有代码?
解决方案
一、视频中大概率使用的组件:DraggableScrollableSheet
这个组件是Flutter官方提供的,天生支持:
- 拖拽调整底部弹窗的高度
- 内部可嵌套滚动组件(如ListView)实现内容滚动
- 仅构建当前显示的内容,不会一次性加载所有子组件
完全匹配你提到的视频中底部弹窗的特性。
二、替换IndexedStack的具体实现
1. 核心修改:用DraggableScrollableSheet替代Positioned+IndexedStack
修改BottomBar的代码,结合Provider的Selector实现按需渲染和动态切换:
@override Widget build(BuildContext context) { return Selector<ProviderService, int>( selector: (ctx, prod) => prod.indexWidgetInformationPicker, shouldRebuild: (nPage, oldPage) => nPage != oldPage, builder: (ctx, indexPage, child) { // 控制弹窗是否显示,比如index为-1时隐藏 if (indexPage == -1) return const SizedBox.shrink(); return DraggableScrollableSheet( initialChildSize: 0.325, // 对应原Positioned的top:67.5h(屏幕高度占比32.5%) minChildSize: 0.1, // 最小高度,拖拽到此处可触发关闭 maxChildSize: 0.9, // 最大可展开高度 expand: false, builder: (context, scrollController) { // 根据当前index返回对应组件 Widget currentChild; switch(indexPage) { case 0: currentChild = pickUpDestination( widget: widget, my_loc_controller: my_loc_controller, ); break; case 1: currentChild = BestRouteCalculator( colorizeTextStyle: colorizeTextStyle, colorizeColors: colorizeColors, ); break; case 2: currentChild = pickRide( widget: widget, my_loc_controller: my_loc_controller, ); break; case 3: currentChild = FindADriver( key: UniqueKey(), colorizeTextStyle: colorizeTextStyle, colorizeColors: colorizeColors, ); break; case 4: currentChild = RideStarted(); break; default: currentChild = const SizedBox.shrink(); } // 包裹ListView实现滚动,添加拖拽指示器和关闭按钮 return ListView( controller: scrollController, shrinkWrap: true, children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 拖拽指示器 Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), // 关闭按钮 IconButton( icon: const Icon(Icons.close), onPressed: () { // 触发关闭逻辑,比如更新Provider的index为-1 Provider.of<ProviderService>(context, listen: false) .updateIndex(-1); }, ), ], ), ), currentChild, ], ); }, ); }, ); }
2. 关键特性说明
- 按需构建:通过
switch-case仅返回当前index对应的组件,解决了IndexedStack一次性构建所有子组件的问题 - 大小调整:通过
initialChildSize/minChildSize/maxChildSize控制弹窗的初始、最小、最大高度,用户还能拖拽调整 - 滚动支持:内部用
ListView包裹子组件,配合scrollController实现内容滚动 - 滑动关闭:拖拽弹窗到
minChildSize的高度时,可在Provider中监听高度变化触发关闭;也可通过顶部的关闭按钮手动关闭
内容的提问来源于stack exchange,提问作者Pannam T
相关产品推荐
相关产品推荐

