Flutter地图标记增强插件google_maps_marker_widgets的使用
Flutter 地图标记增强插件 google_maps_marker_widgets 的使用
Google Maps Marker Widgets
google_maps_marker_widgets
是一个 Flutter 包,用于在 Google 地图上使用任意小部件作为标记。
特性
- 可以使用 任何 小部件作为
[Marker]
显示在[GoogleMap]
上。 - 更新标记位置时具有平滑动画(无瞬移)。
- 可自定义的
[LocationPuck]
小部件,允许手动控制位置更新和外观。
截图
动画标记小部件 | 自定义位置指针 |
---|---|
开始使用
安装和设置 google_maps_flutter
如果你还没有安装和设置 google_maps_flutter
,请先进行安装:
flutter pub add google_maps_flutter
添加权限
iOS
在 Info.plist
文件中添加权限以访问用户的地理位置。
对于仅在使用时访问:
<key>NSLocationWhenInUseUsageDescription</key>
<string>允许此应用在使用时访问您的位置?</string>
对于在后台也监控位置数据的应用:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>此应用在任何时候跟踪您的位置会表现得更好。</string>
Android
打开 app/src/main/AndroidManifest.xml
并添加以下权限之一:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
添加 geolocator
和 flutter_compass
(可选)
flutter pub add geolocator
flutter pub add flutter_compass
使用方法
google_maps_marker_widgets
有三个主要组件:
1. [MarkerWidget]
- 在 [GoogleMap]
中为 [Marker]
提供视觉内容的小部件
final treeMarkerId = MarkerId('treeMarker');
final treeMarkerWidget = MarkerWidget(
markerId: treeMarkerId,
child: Icon(Icons.park, color: Colors.green, size: 45),
);
2. [MarkerWidgetsController]
- 管理 [MarkerWidget]
及其关联的 [Marker]
在 [GoogleMap]
上
你可以使用 markerWidgetsController
来添加、删除和更新标记。
创建控制器并添加标记
final markerWidgetsController = MarkerWidgetsController();
final treeMarker = Marker(
markerId: treeMarkerId,
anchor: Offset(0.5, 0.5),
position: LatLng(37, -108)
);
markerWidgetsController.addMarkerWidget(
markerWidget: treeMarkerWidget,
marker: treeMarker
);
注意 [Marker.anchor]
被设置为 Offset(0.5, 0.5)
。这确保了标记在位置上居中。默认情况下,来自 [GoogleMap]
的锚点是 Offset(0.5, 1.0)
。
更新标记的位置
final treeMarker = markerWidgetsController.markerForId(treeMarkerId)!;
final newPosition = LatLng(33, -105);
final updatedMarker = treeMarker.copyWith(positionParam: newPosition);
markerWidgetsController.updateMarker(marker: updatedMarker);
当更新标记时,默认情况下会使用动画。你可以通过传递 animated: false
来禁用此行为。
3. [MarkerWidgets]
- 用于 [GoogleMap]
的包装小部件
[MarkerWidgets]
是使用 google_maps_marker_widgets
的主要入口点。
将 [MarkerWidgetsController]
传递给 [MarkerWidgets]
,并在 [MarkerWidgets.builder]
方法中创建你的 [GoogleMap]
,传入提供的标记集合。
MarkerWidgets(
markerWidgetsController: _markerWidgetsController,
builder: (context, markers) => GoogleMap(
initialCameraPosition: CameraPosition(target: LatLng(41.8, -99.65), zoom: 4),
markers: markers,
),
)
完整示例
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_compass/flutter_compass.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_marker_widgets/google_maps_marker_widgets.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Google Maps Marker Widgets Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightGreen),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final MarkerWidgetsController _markerWidgetsController = MarkerWidgetsController();
StreamSubscription<Position>? _deviceLocationStream;
StreamSubscription<CompassEvent>? _compassEventsStream;
CameraPosition? _cameraPosition;
GoogleMapController? _mapController;
[@override](/user/override)
void initState() {
_addSampleWidgetMarkers();
super.initState();
}
[@override](/user/override)
void dispose() {
_deviceLocationStream?.cancel();
_compassEventsStream?.cancel();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
body: MarkerWidgets(
markerWidgetsController: _markerWidgetsController,
builder: (context, markers) => GoogleMap(
onMapCreated: _onMapCreated,
onCameraMove: _onCameraMove,
initialCameraPosition: CameraPosition(target: LatLng(41.8, -99.65), zoom: 4),
markers: markers,
myLocationButtonEnabled: false,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _moveMarkers,
child: Icon(Icons.play_arrow_rounded),
),
);
}
void _onMapCreated(GoogleMapController mapController) async {
_mapController = mapController;
try {
// 检查权限并开始流式位置数据
await _startLocationDataStreaming();
final currentLocation = await Geolocator.getCurrentPosition();
final newLatLng = LatLng(currentLocation.latitude, currentLocation.longitude);
final cameraUpdate = CameraUpdate.newLatLng(newLatLng);
await _mapController?.animateCamera(cameraUpdate);
} catch (error) {
// TODO: 处理错误
}
}
void _onCameraMove(CameraPosition cameraPosition) {
_cameraPosition = cameraPosition;
}
void _addSampleWidgetMarkers() {
// 添加一些示例小部件以演示动画
// 注意锚点 / Offset 设置为 0.5, 0.5
// 这会将您的小部件居中在所提供的位置上
final treeMarkerId = MarkerId('treeMarker');
_markerWidgetsController.addMarkerWidget(
markerWidget: MarkerWidget(
markerId: treeMarkerId,
child: Icon(Icons.park, color: Colors.green, size: 45),
),
marker: Marker(
markerId: treeMarkerId,
anchor: Offset(0.5, 0.5),
position: LatLng(37, -108),
),
);
final smileyMarkerId = MarkerId('smileyMarker');
_markerWidgetsController.addMarkerWidget(
markerWidget: MarkerWidget(
markerId: smileyMarkerId,
child: Text(
'😀',
style: TextStyle(fontSize: 40),
),
),
marker: Marker(
markerId: smileyMarkerId,
position: LatLng(37, -101),
anchor: Offset(0.5, 0.5),
),
);
final flightMarkerId = MarkerId('flightMarker');
_markerWidgetsController.addMarkerWidget(
markerWidget: MarkerWidget(
markerId: flightMarkerId,
child: CircleAvatar(
backgroundColor: Colors.blue,
child: Icon(
Icons.flight,
color: Colors.white,
),
),
),
marker: Marker(
markerId: flightMarkerId,
anchor: Offset(0.5, 0.5),
position: LatLng(37, -93.65),
),
);
}
// 将标记移动到新随机位置
void _moveMarkers() {
final markers = _markerWidgetsController.markers.value;
for (var marker in markers) {
if (marker.markerId != MarkerId('device')) {
final newLat = 41.8 + Random().nextDouble() * 10 * (Random().nextBool() ? -1 : 1);
final newLng = -99 + Random().nextDouble() * 10 * (Random().nextBool() ? -1 : 1);
final newMarker = marker.copyWith(positionParam: LatLng(newLat, newLng));
_markerWidgetsController.updateMarker(newMarker);
}
}
}
Future<void> _startLocationDataStreaming() async {
bool serviceEnabled;
LocationPermission permission;
// 测试位置服务是否启用
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// 位置服务未启用,不要继续访问位置并请求用户启用位置服务
return Future.error('位置服务已禁用。');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// 权限被拒绝,下次可以再次请求权限(这是 Android 的 shouldShowRequestPermissionRationale 返回 true 的情况。根据 Android 指南,您的应用此时应显示解释性 UI)
return Future.error('位置权限被拒绝');
}
}
if (permission == LocationPermission.deniedForever) {
// 权限被永久拒绝,处理这种情况
return Future.error('位置权限被永久拒绝,我们无法请求权限。');
}
// 开始流式当前位置
_deviceLocationStream = Geolocator.getPositionStream().listen(_onDeviceLocationUpdate);
_compassEventsStream = FlutterCompass.events?.listen(_onCompassEvent);
}
void _onCompassEvent(CompassEvent compassEvent) {
final currentHeading = compassEvent.heading;
final marker = _markerWidgetsController.markerForId(MarkerId('device'));
if (marker != null) {
double rotationParam = (currentHeading ?? 0) - (_cameraPosition?.bearing ?? 0);
final newMarker = marker.copyWith(rotationParam: rotationParam);
_markerWidgetsController.updateMarker(newMarker, animated: false);
}
}
void _onDeviceLocationUpdate(Position position) {
final newLatLng = LatLng(position.latitude, position.longitude);
final deviceMaker = _markerWidgetsController.markerForId(MarkerId('device'));
if (deviceMaker == null) {
// 添加设备标记到地图
_markerWidgetsController.addDeviceLocationPuck(
initialPosition: newLatLng,
locationPuck: LocationPuck(showHeading: true),
);
} else {
final positionParam = LatLng(position.latitude, position.longitude);
final newDeviceMarker = deviceMaker.copyWith(positionParam: positionParam);
_markerWidgetsController.updateMarker(newDeviceMarker);
}
}
}
更多关于Flutter地图标记增强插件google_maps_marker_widgets的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter地图标记增强插件google_maps_marker_widgets的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用 google_maps_marker_widgets
插件在 Flutter 中增强地图标记的示例代码。这个插件提供了一些高级功能,比如自定义标记、标记集群等。
首先,确保在你的 pubspec.yaml
文件中添加依赖项:
dependencies:
flutter:
sdk: flutter
google_maps_flutter: ^2.1.1 # 确保使用兼容的版本
google_maps_marker_widgets: ^2.0.0 # 确保使用兼容的版本
然后运行 flutter pub get
来获取依赖项。
接下来是一个完整的 Flutter 应用示例,展示了如何使用 google_maps_marker_widgets
插件:
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_marker_widgets/google_maps_marker_widgets.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Google Maps Marker Widgets Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MapScreen(),
);
}
}
class MapScreen extends StatefulWidget {
@override
_MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
final Completer<GoogleMapController> _controller = Completer();
Set<Marker> _markers = Set<Marker>();
Set<MarkerWithInfoWindow> _customMarkers = Set<MarkerWithInfoWindow>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Google Maps Marker Widgets Demo'),
),
body: GoogleMap(
mapType: MapType.hybrid,
initialCameraPosition: CameraPosition(
target: LatLng(37.7749, -122.4194),
zoom: 11.0,
),
markers: Set<Marker>.of(_markers),
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
_addMarkers();
},
myLocationEnabled: true,
zoomGesturesEnabled: true,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_showMarkerInfoWindow();
},
tooltip: 'Show Marker Info Window',
child: Icon(Icons.info),
),
);
}
void _addMarkers() {
setState(() {
_markers.add(Marker(
markerId: MarkerId('marker1'),
position: LatLng(37.7749, -122.4194),
infoWindow: InfoWindow(title: 'Marker 1', snippet: 'San Francisco'),
));
// Using google_maps_marker_widgets for custom markers
_customMarkers.add(MarkerWithInfoWindow(
markerId: MarkerId('custom_marker1'),
position: LatLng(37.7649, -122.4394),
marker: Marker(
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueAzure),
),
infoWindow: InfoWindow(
title: 'Custom Marker',
snippet: 'This is a custom marker with google_maps_marker_widgets',
),
infoWindowBuilder: (context, markerId, onTap) {
return Container(
height: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Custom Info Window'),
ElevatedButton(
onPressed: () => onTap(),
child: Text('Close'),
),
],
),
);
},
));
// Note: google_maps_marker_widgets does not directly modify the markers set of GoogleMap,
// so you need to manage the markers manually and use MarkerLayer to render custom markers.
// Below is a conceptual example of how you might integrate MarkerLayer if needed.
// However, for simplicity, we'll just keep the markers in _customMarkers and handle info windows separately.
});
}
void _showMarkerInfoWindow() {
_controller.future.then((controller) {
if (_customMarkers.isNotEmpty) {
final MarkerWithInfoWindow marker = _customMarkers.first;
controller.showMarkerInfoWindow(marker.markerId);
}
});
}
}
注意事项
-
MarkerLayer 的使用:
- 在上面的代码中,我们没有直接使用
MarkerLayer
,因为google_maps_marker_widgets
插件主要提供的是自定义信息窗口的功能。 - 如果你需要更复杂的自定义标记(例如带有动画或复杂布局的标记),你可能需要结合
MarkerLayer
和Stack
小部件来自行管理这些标记的渲染。
- 在上面的代码中,我们没有直接使用
-
InfoWindowBuilder:
MarkerWithInfoWindow
类允许你使用infoWindowBuilder
来自定义信息窗口。- 在上面的例子中,我们定义了一个简单的自定义信息窗口,包含一个文本和一个关闭按钮。
-
错误处理:
- 在实际应用中,添加错误处理逻辑来处理例如地图加载失败、标记点击事件等情况。
-
依赖版本:
- 确保
google_maps_flutter
和google_maps_marker_widgets
插件的版本是兼容的。
- 确保
这个示例展示了如何使用 google_maps_marker_widgets
插件在 Flutter 应用中增强地图标记的功能。希望这对你有所帮助!