Flutter中点击Google Map Marker后显示Bottom Sheet的实现方法
实现Flutter Google Map Marker点击显示Bottom Sheet
嘿,这个需求我之前做过好多次,实现起来思路很清晰,我给你拆解成具体步骤,附上完整代码示例:
1. 先搞定依赖
首先确保你的pubspec.yaml里引入了google_maps_flutter(毕竟要用到地图),如果还没加的话,加上这行:
dependencies: flutter: sdk: flutter google_maps_flutter: ^2.5.0 # 换成最新版本就行
然后跑一下flutter pub get拉取依赖。
2. 初始化Google Map与Marker
先创建一个StatefulWidget,因为我们需要管理地图控制器和Marker的状态。核心是给每个Marker绑定onTap回调,这是触发Bottom Sheet的入口。
import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; class MapWithMarkerBottomSheet extends StatefulWidget { const MapWithMarkerBottomSheet({super.key}); @override State<MapWithMarkerBottomSheet> createState() => _MapWithMarkerBottomSheetState(); } class _MapWithMarkerBottomSheetState extends State<MapWithMarkerBottomSheet> { late GoogleMapController _mapController; // 模拟的地点数据,你可以换成自己的数据源 final List<Map<String, dynamic>> _places = [ { 'id': '1', 'name': '中央公园', 'latLng': const LatLng(40.7829, -73.9654), 'description': '纽约最著名的城市公园,占地843英亩', 'address': '纽约市曼哈顿区' }, { 'id': '2', 'name': '帝国大厦', 'latLng': const LatLng(40.7484, -73.9857), 'description': '标志性摩天大楼,曾是世界最高建筑', 'address': '纽约市曼哈顿第五大道350号' } ]; // 生成Marker集合 Set<Marker> _buildMarkers() { return _places.map((place) { return Marker( markerId: MarkerId(place['id']), position: place['latLng'], infoWindow: InfoWindow(title: place['name']), // 可选:默认的InfoWindow可留可删 onTap: () => _showPlaceBottomSheet(place), // 关键:点击触发Bottom Sheet ); }).toSet(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('地图地点详情')), body: GoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(40.7128, -74.0060), // 纽约坐标作为初始位置 zoom: 12, ), onMapCreated: (controller) => _mapController = controller, markers: _buildMarkers(), ), ); } @override void dispose() { _mapController.dispose(); // 记得释放控制器 super.dispose(); } }
3. 实现Bottom Sheet弹窗逻辑
接下来写_showPlaceBottomSheet方法,用Flutter自带的showModalBottomSheet构建弹窗,里面可以自定义任意你需要的详情UI:
void _showPlaceBottomSheet(Map<String, dynamic> place) { showModalBottomSheet( context: context, isScrollControlled: true, // 可选:内容太长时允许滚动并适配屏幕高度 shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (context) { return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, // 适配键盘(如果有输入框的话) ), child: Container( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, // 让高度自适应内容 crossAxisAlignment: CrossAxisAlignment.start, children: [ // 顶部标题栏,可选:加个关闭按钮 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( place['name'], style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ], ), const SizedBox(height: 12), // 地点地址 Text( '地址:${place['address']}', style: const TextStyle(color: Colors.grey), ), const SizedBox(height: 12), // 地点描述 Text(place['description']), const SizedBox(height: 20), // 可以加更多功能按钮,比如导航、分享 Row( children: [ Expanded( child: ElevatedButton( onPressed: () { // 这里可以加导航逻辑,比如调用第三方地图应用 Navigator.pop(context); }, child: const Text('导航'), ), ), const SizedBox(width: 12), Expanded( child: OutlinedButton( onPressed: () { // 分享逻辑实现 Navigator.pop(context); }, child: const Text('分享'), ), ), ], ), ], ), ), ); }, ); }
几个实用小技巧
- 控制Bottom Sheet高度:如果不想让弹窗占满屏幕,把
isScrollControlled设为false,或者给Container指定固定高度。 - 异步数据加载:如果地点详情需要从网络获取,在
_showPlaceBottomSheet里先显示加载状态(比如CircularProgressIndicator),请求完成后再渲染详情UI。 - Marker交互增强:点击Marker时,可以给Marker加缩放或颜色变化的动画,比如修改Marker的
icon属性,提升用户体验。 - 多屏幕适配:用
MediaQuery调整弹窗的padding和大小,确保在手机和平板上都有良好的显示效果。
这样一套下来,点击地图上的Marker就能弹出漂亮的Bottom Sheet展示地点详情啦~
内容的提问来源于stack exchange,提问作者Deepika Udayakumar




