鸿蒙 Flutter 地图开发进阶:高德 / 百度地图集成与自定义图层(POI / 轨迹)
本文详细介绍了鸿蒙Flutter应用中集成高德/百度地图SDK的开发实践,涵盖基础地图展示、POI搜索标注和轨迹绘制三大核心功能。从开发环境配置、权限申请、SDK初始化到功能实现,提供了完整代码示例和优化建议。重点讲解了地图选型策略、POI搜索防抖处理、轨迹抽稀算法等关键技术,并分享了性能优化技巧和常见问题解决方案。通过实战案例展示如何整合这些功能,为开发者提供了鸿蒙生态下地图开发的完整指南。
在鸿蒙生态跨平台开发中,地图功能是出行、物流、本地生活类应用的核心模块。Flutter 凭借其跨平台一致性和高性能渲染优势,成为鸿蒙应用开发的优选框架。本文将从实战角度出发,详细讲解如何在鸿蒙 Flutter 应用中集成高德 / 百度地图 SDK,实现基础地图展示、POI 搜索与标注、轨迹绘制等进阶功能,并提供完整可复用的代码示例和避坑指南,帮助开发者快速落地地图相关需求。
一、前置准备:开发环境与核心依赖
1.1 开发环境配置
-
基础环境:
- 鸿蒙 SDK:API Version 9+(需支持 Flutter 跨平台能力,推荐 API 10 稳定版)
- Flutter 版本:3.10+(确保与鸿蒙 Flutter 插件兼容,参考 Flutter 鸿蒙适配指南)
- DevEco Studio:4.0+(鸿蒙应用开发 IDE,集成 Flutter 开发插件)
- 地图平台账号:高德地图开放平台、百度地图开放平台(需申请开发者账号并创建应用,获取 API Key)
-
环境验证:运行以下命令确认 Flutter 与鸿蒙环境适配:
bash
运行
flutter doctor --harmony若输出 “HarmonyOS toolchain - develop for HarmonyOS” 且无错误,说明环境配置成功。
1.2 核心依赖库选择
鸿蒙 Flutter 地图开发需依赖支持鸿蒙系统的地图插件,以下是经过实战验证的稳定插件:
| 地图平台 | 推荐插件 | 插件地址 | 核心优势 |
|---|---|---|---|
| 高德地图 | amap_flutter_map |
pub.dev 地址 | 鸿蒙适配完善,支持自定义图层、定位、POI 搜索 |
| 百度地图 | baidu_map_flutter |
GitHub 地址 | 支持离线地图,轨迹绘制功能丰富 |
依赖配置步骤:
-
在
pubspec.yaml中添加插件依赖:yaml
dependencies: flutter: sdk: flutter # 高德地图依赖(二选一) amap_flutter_map: ^3.0.0 # 需与鸿蒙 SDK 版本匹配 amap_flutter_location: ^5.0.0 # 定位辅助插件 # 百度地图依赖(二选一) baidu_map_flutter: ^2.1.0 baidu_map_flutter_location: ^1.0.0 # 其他辅助依赖 permission_handler: ^10.2.0 # 权限申请 dio: ^5.3.0 # 网络请求(POI 搜索接口调用) json_serializable: ^6.7.0 # JSON 解析 -
执行
flutter pub get安装依赖,若出现兼容性问题,可通过flutter pub upgrade更新插件版本。
1.3 地图 API Key 申请
(1)高德地图 API Key 申请
- 访问 高德地图开放平台,注册并创建应用(选择 “鸿蒙应用” 类型)。
- 进入应用管理,添加 “地图 SDK” 和 “POI 搜索” 服务,获取 API Key(需配置应用包名和签名信息)。
- 参考 高德地图鸿蒙 SDK 配置文档 完成权限配置。
(2)百度地图 API Key 申请
- 访问 百度地图开放平台,注册并创建应用(选择 “鸿蒙应用”)。
- 申请 “地图 SDK” 和 “POI 检索” 权限,获取 API Key(需填写应用包名和签名)。
- 配置参考 百度地图鸿蒙 SDK 快速入门。
二、基础地图集成:高德 / 百度地图快速接入
2.1 高德地图集成步骤
(1)权限配置
在鸿蒙应用的 config.json 中添加必要权限:
json
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.LOCATION",
"reason": "获取位置信息用于地图定位",
"usedScene": {
"ability": ["com.example.map_demo.MainAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.INTERNET",
"reason": "访问网络加载地图数据",
"usedScene": {
"ability": ["com.example.map_demo.MainAbility"],
"when": "inuse"
}
}
]
}
}
(2)初始化地图 SDK
在应用入口 main.dart 中初始化高德地图 SDK:
dart
import 'package:amap_flutter_map/amap_flutter_map.dart';
import 'package:amap_flutter_location/amap_flutter_location.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化高德地图 SDK(替换为你的 API Key)
await AMapFlutterMap.initialize("你的高德地图 API Key");
// 初始化定位 SDK
await AMapFlutterLocation().initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "鸿蒙 Flutter 地图 Demo",
theme: ThemeData(primarySwatch: Colors.blue),
home: const MapHomePage(),
);
}
}
(3)基础地图展示
创建 MapHomePage 组件,实现地图加载和基础交互:
dart
class MapHomePage extends StatefulWidget {
const MapHomePage({super.key});
@override
State<MapHomePage> createState() => _MapHomePageState();
}
class _MapHomePageState extends State<MapHomePage> {
// 地图控制器(用于操作地图缩放、平移等)
late AMapController _mapController;
// 初始中心点坐标(北京天安门)
final LatLng _initialCenter = const LatLng(39.9042, 116.4074);
// 初始缩放级别(3-20,数值越大越详细)
final double _initialZoom = 14.0;
// 地图创建完成回调
void _onMapCreated(AMapController controller) {
_mapController = controller;
// 设置地图样式(可选:标准、卫星、黑夜模式)
_mapController.setMapStyle(AMapMapStyle.standard);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("高德地图基础展示")),
body: AMapWidget(
initialCameraPosition: CameraPosition(
target: _initialCenter,
zoom: _initialZoom,
),
onMapCreated: _onMapCreated,
// 启用定位功能
myLocationStyleOptions: MyLocationStyleOptions(
enabled: true,
myLocationType: MyLocationType.show,
icon: BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(30, 30)),
"images/location_marker.png", // 自定义定位图标
),
),
// 启用地图交互(缩放、平移、旋转)
interactive: true,
zoomGesturesEnabled: true,
scrollGesturesEnabled: true,
rotateGesturesEnabled: true,
),
);
}
@override
void dispose() {
// 释放地图资源
_mapController.dispose();
AMapFlutterLocation().dispose();
super.dispose();
}
}
2.2 百度地图集成步骤
(1)权限配置
与高德地图一致,需在 config.json 中添加定位和网络权限。
(2)初始化百度地图 SDK
dart
import 'package:baidu_map_flutter/baidu_map_flutter.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化百度地图 SDK(替换为你的 API Key)
await BaiduMapSdk.initSdk("你的百度地图 API Key");
runApp(const MyApp());
}
(3)基础地图展示
dart
class BaiduMapHomePage extends StatefulWidget {
const BaiduMapHomePage({super.key});
@override
State<BaiduMapHomePage> createState() => _BaiduMapHomePageState();
}
class _BaiduMapHomePageState extends State<BaiduMapHomePage> {
late BaiduMapController _mapController;
final LatLng _initialCenter = const LatLng(39.9042, 116.4074);
final double _initialZoom = 14.0;
void _onMapCreated(BaiduMapController controller) {
_mapController = controller;
// 设置地图类型(普通、卫星、交通)
_mapController.setMapType(MapType.normal);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("百度地图基础展示")),
body: BaiduMap(
initialCameraPosition: CameraPosition(
target: _initialCenter,
zoom: _initialZoom,
),
onMapCreated: _onMapCreated,
// 定位配置
locationEnabled: true,
myLocationConfig: MyLocationConfig(
myLocationType: MyLocationType.locationAndRotate,
icon: BitmapDescriptor.fromAsset("images/location_marker.png"),
),
// 交互配置
zoomGesturesEnabled: true,
scrollGesturesEnabled: true,
),
);
}
@override
void dispose() {
_mapController.dispose();
super.dispose();
}
}
2.3 基础功能对比与选型建议
| 功能点 | 高德地图 | 百度地图 |
|---|---|---|
| 鸿蒙适配度 | ★★★★★ | ★★★★☆ |
| 地图加载速度 | 较快 | 快 |
| 定位精度 | 高(支持 GPS + 网络定位) | 高(支持北斗定位) |
| 离线地图 | 支持 | 支持(文档更详细) |
| 自定义样式 | 丰富(支持自定义地图主题) | 一般(基础样式可配置) |
选型建议:
- 若需丰富的自定义地图样式和 POI 搜索功能,优先选择高德地图;
- 若需离线地图和北斗定位支持,优先选择百度地图;
- 若应用需同时兼容多平台(鸿蒙 + Android+iOS),建议封装统一地图接口,根据平台动态切换 SDK。
三、进阶功能:POI 搜索与标注
POI(Point of Interest)即兴趣点,是地图应用中常用的功能(如搜索餐厅、加油站、公交站等)。本节将讲解如何通过高德 / 百度地图 SDK 实现 POI 搜索,并在地图上标注结果。
3.1 高德地图 POI 搜索实现
(1)POI 搜索 API 调用
使用 amap_flutter_search 插件或直接调用高德地图 Web API 实现搜索。以下是 Web API 方式(无需额外插件,灵活性更高):
dart
import 'package:dio/dio.dart';
import 'dart:convert';
// POI 搜索模型
class PoiSearchResult {
final String name; // 名称
final LatLng location; // 坐标
final String address; // 地址
final String type; // 类型
PoiSearchResult({
required this.name,
required this.location,
required this.address,
required this.type,
});
factory PoiSearchResult.fromJson(Map<String, dynamic> json) {
return PoiSearchResult(
name: json["name"],
location: LatLng(
double.parse(json["location"].split(",")[1]),
double.parse(json["location"].split(",")[0]),
),
address: json["address"],
type: json["type"],
);
}
}
class AMapPoiSearch {
static const String _apiKey = "你的高德地图 API Key";
static const String _searchUrl = "https://restapi.amap.com/v3/place/text";
// 关键词搜索 POI
static Future<List<PoiSearchResult>> searchByKeyword({
required String keyword,
required LatLng center,
int radius = 5000, // 搜索半径(米)
int pageSize = 20,
int pageNum = 1,
}) async {
try {
final response = await Dio().get(_searchUrl, queryParameters: {
"key": _apiKey,
"keywords": keyword,
"location": "${center.longitude},${center.latitude}",
"radius": radius,
"page_size": pageSize,
"page_num": pageNum,
"output": "json",
});
if (response.statusCode == 200) {
final data = json.decode(response.data);
if (data["status"] == "1") {
final List<dynamic> pois = data["pois"];
return pois.map((poi) => PoiSearchResult.fromJson(poi)).toList();
} else {
throw Exception("POI 搜索失败:${data["info"]}");
}
} else {
throw Exception("网络请求失败:${response.statusCode}");
}
} catch (e) {
debugPrint("POI 搜索异常:$e");
return [];
}
}
}
(2)POI 结果标注在地图上
修改 MapHomePage,添加搜索框和 POI 标注:
dart
class _MapHomePageState extends State<MapHomePage> {
final TextEditingController _searchController = TextEditingController();
List<PoiSearchResult> _poiResults = [];
// 存储 POI 标注点
final List<Marker> _poiMarkers = [];
// 搜索 POI 并更新标注
Future<void> _searchPoi(String keyword) async {
if (keyword.isEmpty) return;
// 获取当前地图中心点
final CameraPosition currentCamera = await _mapController.getCameraPosition();
final List<PoiSearchResult> results = await AMapPoiSearch.searchByKeyword(
keyword: keyword,
center: currentCamera.target,
);
setState(() {
_poiResults = results;
// 清除旧标注
_poiMarkers.clear();
// 添加新标注
for (int i = 0; i < results.length; i++) {
final poi = results[i];
_poiMarkers.add(
Marker(
markerId: MarkerId("poi_$i"),
position: poi.location,
icon: BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(30, 30)),
"images/poi_marker.png", // 自定义 POI 图标
),
infoWindow: InfoWindow(
title: poi.name,
snippet: poi.address,
onTap: () {
// 点击信息窗回调
debugPrint("点击了 ${poi.name}");
},
),
onTap: () {
// 点击标注点显示信息窗
_mapController.showInfoWindow(MarkerId("poi_$i"));
},
),
);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("高德地图 POI 搜索")),
body: Stack(
children: [
AMapWidget(
initialCameraPosition: CameraPosition(
target: _initialCenter,
zoom: _initialZoom,
),
onMapCreated: _onMapCreated,
myLocationStyleOptions: MyLocationStyleOptions(enabled: true),
markers: Set<Marker>.of(_poiMarkers), // 添加 POI 标注
),
// 搜索框
Positioned(
top: 20,
left: 20,
right: 20,
child: Row(
children: [
Expanded(
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: "搜索餐厅、加油站、公交站...",
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
),
),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => _searchPoi(_searchController.text),
child: const Text("搜索"),
),
],
),
),
],
),
);
}
}
3.2 百度地图 POI 搜索实现
百度地图 POI 搜索类似,以下是核心代码:
dart
// 百度地图 POI 搜索 API
class BaiduPoiSearch {
static const String _apiKey = "你的百度地图 API Key";
static const String _searchUrl = "https://api.map.baidu.com/place/v2/search";
static Future<List<PoiSearchResult>> searchByKeyword({
required String keyword,
required LatLng center,
int radius = 5000,
}) async {
try {
final response = await Dio().get(_searchUrl, queryParameters: {
"ak": _apiKey,
"query": keyword,
"location": "${center.latitude},${center.longitude}",
"radius": radius,
"output": "json",
"scope": 2, // 返回详细信息
});
if (response.statusCode == 200) {
final data = json.decode(response.data);
if (data["status"] == 0) {
final List<dynamic> results = data["results"];
return results.map((poi) => PoiSearchResult(
name: poi["name"],
location: LatLng(
poi["location"]["lat"],
poi["location"]["lng"],
),
address: poi["address"],
type: poi["detail_info"]["type"],
)).toList();
} else {
throw Exception("POI 搜索失败:${data["message"]}");
}
} else {
throw Exception("网络请求失败");
}
} catch (e) {
debugPrint("POI 搜索异常:$e");
return [];
}
}
}
// 百度地图 POI 标注
class _BaiduMapHomePageState extends State<BaiduMapHomePage> {
final TextEditingController _searchController = TextEditingController();
List<PoiSearchResult> _poiResults = [];
final List<Marker> _poiMarkers = [];
Future<void> _searchPoi(String keyword) async {
if (keyword.isEmpty) return;
final CameraPosition currentCamera = await _mapController.getCameraPosition();
final List<PoiSearchResult> results = await BaiduPoiSearch.searchByKeyword(
keyword: keyword,
center: currentCamera.target,
);
setState(() {
_poiResults = results;
_poiMarkers.clear();
for (int i = 0; i < results.length; i++) {
final poi = results[i];
_poiMarkers.add(
Marker(
markerId: MarkerId("poi_$i"),
position: poi.location,
icon: BitmapDescriptor.fromAsset("images/poi_marker.png"),
infoWindow: InfoWindow(
title: poi.name,
snippet: poi.address,
),
),
);
}
});
}
// 构建 UI 部分与高德地图类似,替换 AMapWidget 为 BaiduMap 即可
}
3.3 POI 搜索优化技巧
- 搜索半径动态调整:根据地图缩放级别调整搜索半径(缩放级别越高,半径越小)。
- 防抖处理:使用
Debouncer避免频繁搜索(输入完成后延迟 500ms 再发起请求):dart
class Debouncer { final int milliseconds; VoidCallback? _action; Timer? _timer; Debouncer({this.milliseconds = 500}); run(VoidCallback action) { _timer?.cancel(); _timer = Timer(Duration(milliseconds: milliseconds), action); } } // 使用示例 final Debouncer _debouncer = Debouncer(); TextField( onChanged: (value) { _debouncer.run(() => _searchPoi(value)); }, ); - 缓存搜索结果:使用
shared_preferences缓存历史搜索结果,减少重复网络请求。 - 权限判断:搜索前检查网络权限,无网络时提示用户。
四、高级功能:自定义轨迹图层绘制
轨迹绘制是运动、物流类应用的核心需求(如跑步轨迹、车辆行驶轨迹)。本节将讲解如何在地图上绘制自定义轨迹,支持轨迹颜色、宽度、透明度配置,以及轨迹动画效果。
4.1 高德地图轨迹绘制
(1)轨迹数据模型
dart
// 轨迹点模型
class TrackPoint {
final LatLng location; // 坐标
final double speed; // 速度(km/h)
final int timestamp; // 时间戳(毫秒)
TrackPoint({
required this.location,
required this.speed,
required this.timestamp,
});
}
// 轨迹线模型
class TrackLine {
final List<TrackPoint> points; // 轨迹点列表
final Color color; // 轨迹颜色
final double width; // 轨迹宽度(像素)
final double opacity; // 透明度(0-1)
TrackLine({
required this.points,
this.color = Colors.blue,
this.width = 5.0,
this.opacity = 1.0,
});
}
(2)轨迹绘制实现
通过 Polyline 组件绘制轨迹线,支持动态添加轨迹点:
dart
class _MapHomePageState extends State<MapHomePage> {
// 模拟轨迹数据(北京二环部分路段)
final List<TrackPoint> _mockTrackPoints = [
TrackPoint(
location: const LatLng(39.9042, 116.4074),
speed: 30.0,
timestamp: 1690000000000,
),
TrackPoint(
location: const LatLng(39.9142, 116.4174),
speed: 35.0,
timestamp: 1690000010000,
),
TrackPoint(
location: const LatLng(39.9242, 116.4274),
speed: 40.0,
timestamp: 1690000020000,
),
// 更多轨迹点...
];
// 轨迹线
late Polyline _trackPolyline;
@override
void initState() {
super.initState();
// 初始化轨迹线
_trackPolyline = Polyline(
polylineId: const PolylineId("track_line"),
points: _mockTrackPoints.map((p) => p.location).toList(),
color: Colors.red.withOpacity(0.8),
width: 6.0,
jointType: JointType.round, // 线条拐点圆角
capType: CapType.round, // 线条端点圆角
);
}
// 动态添加轨迹点
void _addTrackPoint(TrackPoint point) {
setState(() {
_mockTrackPoints.add(point);
_trackPolyline = _trackPolyline.copyWith(
pointsParam: _mockTrackPoints.map((p) => p.location).toList(),
);
});
// 移动地图视角到最新轨迹点
_mapController.moveCamera(
CameraUpdate.newLatLng(point.location),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("高德地图轨迹绘制")),
body: Stack(
children: [
AMapWidget(
initialCameraPosition: CameraPosition(
target: _initialCenter,
zoom: _initialZoom,
),
onMapCreated: _onMapCreated,
polylines: {_trackPolyline}, // 添加轨迹线
markers: {
// 轨迹起点标注
Marker(
markerId: const MarkerId("start_point"),
position: _mockTrackPoints.first.location,
icon: BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(30, 30)),
"images/start_marker.png",
),
infoWindow: const InfoWindow(title: "起点"),
),
// 轨迹终点标注
Marker(
markerId: const MarkerId("end_point"),
position: _mockTrackPoints.last.location,
icon: BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(30, 30)),
"images/end_marker.png",
),
infoWindow: const InfoWindow(title: "终点"),
),
},
),
// 控制按钮
Positioned(
bottom: 20,
left: 20,
child: ElevatedButton(
onPressed: () {
// 模拟添加新轨迹点(每 1 秒添加一个)
Timer.periodic(const Duration(seconds: 1), (timer) {
if (_mockTrackPoints.length >= 20) {
timer.cancel();
return;
}
_addTrackPoint(
TrackPoint(
location: LatLng(
_mockTrackPoints.last.location.latitude + 0.001,
_mockTrackPoints.last.location.longitude + 0.001,
),
speed: 45.0,
timestamp: DateTime.now().millisecondsSinceEpoch,
),
);
});
},
child: const Text("开始绘制轨迹"),
),
),
],
),
);
}
}
4.2 百度地图轨迹绘制
百度地图轨迹绘制与高德地图类似,使用 Polyline 组件:
dart
class _BaiduMapHomePageState extends State<BaiduMapHomePage> {
late Polyline _trackPolyline;
final List<TrackPoint> _mockTrackPoints = [/* 模拟轨迹点 */];
@override
void initState() {
super.initState();
_trackPolyline = Polyline(
polylineId: const PolylineId("track_line"),
points: _mockTrackPoints.map((p) => p.location).toList(),
color: Colors.green.withOpacity(0.8),
width: 6,
jointType: JointType.round,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("百度地图轨迹绘制")),
body: BaiduMap(
initialCameraPosition: CameraPosition(
target: _initialCenter,
zoom: _initialZoom,
),
onMapCreated: _onMapCreated,
polylines: {_trackPolyline},
markers: {
Marker(
markerId: const MarkerId("start_point"),
position: _mockTrackPoints.first.location,
icon: BitmapDescriptor.fromAsset("images/start_marker.png"),
),
Marker(
markerId: const MarkerId("end_point"),
position: _mockTrackPoints.last.location,
icon: BitmapDescriptor.fromAsset("images/end_marker.png"),
),
},
),
);
}
}
4.3 轨迹绘制进阶优化
-
轨迹平滑处理:使用抽稀算法(如道格拉斯 - 普克算法)减少轨迹点数量,提升绘制性能:
dart
// 道格拉斯-普克算法抽稀轨迹点 List<TrackPoint> simplifyTrack(List<TrackPoint> points, double tolerance) { if (points.length <= 2) return points; double maxDistance = 0; int index = 0; final LatLng start = points.first.location; final LatLng end = points.last.location; for (int i = 1; i < points.length - 1; i++) { final double distance = _distanceToLine( points[i].location, start, end, ); if (distance > maxDistance) { maxDistance = distance; index = i; } } if (maxDistance > tolerance) { final List<TrackPoint> left = simplifyTrack(points.sublist(0, index + 1), tolerance); final List<TrackPoint> right = simplifyTrack(points.sublist(index), tolerance); return [...left.sublist(0, left.length - 1), ...right]; } else { return [points.first, points.last]; } } // 计算点到直线的距离(米) double _distanceToLine(LatLng point, LatLng start, LatLng end) { // 实现距离计算公式(参考高德/百度地图 SDK 提供的距离计算工具) final double a = start.latitude - end.latitude; final double b = end.longitude - start.longitude; final double c = start.longitude * end.latitude - end.longitude * start.latitude; return (a * point.latitude + b * point.longitude + c).abs() / sqrt(a * a + b * b); } -
轨迹颜色渐变:根据速度动态调整轨迹颜色(如速度越快颜色越红):
dart
Color _getTrackColor(double speed) { if (speed < 30) return Colors.green; if (speed < 60) return Colors.yellow; return Colors.red; } // 分段绘制渐变轨迹 List<Polyline> buildGradientTrack(List<TrackPoint> points) { final List<Polyline> polylines = []; for (int i = 0; i < points.length - 1; i++) { final start = points[i]; final end = points[i + 1]; polylines.add( Polyline( polylineId: PolylineId("track_$i"), points: [start.location, end.location], color: _getTrackColor((start.speed + end.speed) / 2), width: 6.0, ), ); } return polylines; } -
轨迹动画效果:使用
AnimationController实现轨迹动态绘制:dart
late AnimationController _animationController; late Animation<int> _animation; List<LatLng> _animatedPoints = []; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(seconds: 5), ); _animation = IntTween(begin: 0, end: _mockTrackPoints.length - 1).animate( CurvedAnimation(parent: _animationController, curve: Curves.linear), )..addListener(() { setState(() { _animatedPoints = _mockTrackPoints.sublist(0, _animation.value + 1).map((p) => p.location).toList(); _trackPolyline = _trackPolyline.copyWith(pointsParam: _animatedPoints); }); }); } // 启动轨迹动画 void _startTrackAnimation() { _animationController.forward(from: 0); }
五、实战案例:完整地图应用整合
5.1 项目结构设计
plaintext
map_demo/
├── lib/
│ ├── main.dart # 应用入口
│ ├── pages/
│ │ ├── map_home_page.dart # 地图主页面(集成高德/百度地图)
│ ├── models/
│ │ ├── poi_model.dart # POI 数据模型
│ │ ├── track_model.dart # 轨迹数据模型
│ ├── services/
│ │ ├── amap_service.dart # 高德地图服务(POI 搜索、定位)
│ │ ├── baidu_service.dart # 百度地图服务
│ ├── utils/
│ │ ├── debouncer.dart # 防抖工具
│ │ ├── track_simplify.dart # 轨迹抽稀工具
│ ├── res/
│ │ ├── images/ # 地图图标资源
├── config.json # 鸿蒙应用配置(权限、包名等)
├── pubspec.yaml # 依赖配置
5.2 核心功能整合代码
以下是整合了地图展示、POI 搜索、轨迹绘制的完整页面代码(以高德地图为例):
dart
import 'package:flutter/material.dart';
import 'package:amap_flutter_map/amap_flutter_map.dart';
import 'package:amap_flutter_location/amap_flutter_location.dart';
import 'package:dio/dio.dart';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:permission_handler/permission_handler.dart';
// 导入自定义模型和工具
import '../models/poi_model.dart';
import '../models/track_model.dart';
import '../utils/debouncer.dart';
import '../utils/track_simplify.dart';
class MapIntegratePage extends StatefulWidget {
const MapIntegratePage({super.key});
@override
State<MapIntegratePage> createState() => _MapIntegratePageState();
}
class _MapIntegratePageState extends State<MapIntegratePage> with SingleTickerProviderStateMixin {
late AMapController _mapController;
final LatLng _initialCenter = const LatLng(39.9042, 116.4074);
final double _initialZoom = 14.0;
// POI 搜索相关
final TextEditingController _searchController = TextEditingController();
final Debouncer _debouncer = Debouncer(milliseconds: 500);
List<PoiSearchResult> _poiResults = [];
final List<Marker> _poiMarkers = [];
// 轨迹绘制相关
late AnimationController _animationController;
late Animation<int> _animation;
List<TrackPoint> _mockTrackPoints = [/* 模拟轨迹点 */];
List<LatLng> _animatedPoints = [];
late Polyline _trackPolyline;
bool _isTracking = false;
// 权限状态
bool _hasLocationPermission = false;
@override
void initState() {
super.initState();
// 检查定位权限
_checkLocationPermission();
// 初始化轨迹
_initTrack();
// 初始化轨迹动画
_initTrackAnimation();
}
// 检查定位权限
Future<void> _checkLocationPermission() async {
final status = await Permission.location.request();
setState(() {
_hasLocationPermission = status == PermissionStatus.granted;
});
}
// 初始化轨迹
void _initTrack() {
// 模拟轨迹数据
_mockTrackPoints = List.generate(30, (index) {
return TrackPoint(
location: LatLng(
39.9042 + index * 0.001,
116.4074 + index * 0.001,
),
speed: 30 + index * 1.5,
timestamp: DateTime.now().millisecondsSinceEpoch - (30 - index) * 1000,
);
});
// 抽稀轨迹点
final simplifiedPoints = simplifyTrack(_mockTrackPoints, 10); // 抽稀 tolerance 为 10 米
_mockTrackPoints = simplifiedPoints;
// 初始化轨迹线
_trackPolyline = Polyline(
polylineId: const PolylineId("track_line"),
points: _animatedPoints,
color: Colors.red.withOpacity(0.8),
width: 6.0,
jointType: JointType.round,
);
}
// 初始化轨迹动画
void _initTrackAnimation() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 8),
);
_animation = IntTween(begin: 0, end: _mockTrackPoints.length - 1).animate(
CurvedAnimation(parent: _animationController, curve: Curves.linear),
)..addListener(() {
setState(() {
_animatedPoints = _mockTrackPoints.sublist(0, _animation.value + 1).map((p) => p.location).toList();
_trackPolyline = _trackPolyline.copyWith(pointsParam: _animatedPoints);
// 移动地图视角到最新轨迹点
if (_animatedPoints.isNotEmpty) {
_mapController.moveCamera(
CameraUpdate.newLatLng(_animatedPoints.last),
);
}
});
});
}
// 地图创建完成回调
void _onMapCreated(AMapController controller) {
_mapController = controller;
_mapController.setMapStyle(AMapMapStyle.standard);
// 若有定位权限,启用定位
if (_hasLocationPermission) {
_mapController.setMyLocationEnabled(true);
}
}
// POI 搜索
Future<void> _searchPoi(String keyword) async {
if (keyword.isEmpty) return;
final CameraPosition currentCamera = await _mapController.getCameraPosition();
final List<PoiSearchResult> results = await AMapPoiSearch.searchByKeyword(
keyword: keyword,
center: currentCamera.target,
);
setState(() {
_poiResults = results;
_poiMarkers.clear();
for (int i = 0; i < results.length; i++) {
final poi = results[i];
_poiMarkers.add(
Marker(
markerId: MarkerId("poi_$i"),
position: poi.location,
icon: BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(30, 30)),
"images/poi_marker.png",
),
infoWindow: InfoWindow(
title: poi.name,
snippet: poi.address,
),
onTap: () => _mapController.showInfoWindow(MarkerId("poi_$i")),
),
);
}
});
}
// 启动/暂停轨迹绘制
void _toggleTrack() {
setState(() {
_isTracking = !_isTracking;
if (_isTracking) {
_animationController.forward(from: 0);
} else {
_animationController.stop();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("鸿蒙 Flutter 地图综合 Demo"),
actions: [
IconButton(
icon: const Icon(Icons.layers),
onPressed: () {
// 切换地图样式
_mapController.setMapStyle(
_mapController.mapStyle == AMapMapStyle.standard
? AMapMapStyle.dark
: AMapMapStyle.standard,
);
},
),
],
),
body: Stack(
children: [
AMapWidget(
initialCameraPosition: CameraPosition(
target: _initialCenter,
zoom: _initialZoom,
),
onMapCreated: _onMapCreated,
myLocationStyleOptions: MyLocationStyleOptions(
enabled: _hasLocationPermission,
icon: BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(30, 30)),
"images/location_marker.png",
),
),
markers: Set<Marker>.of([
..._poiMarkers,
// 轨迹起点和终点
if (_animatedPoints.isNotEmpty)
Marker(
markerId: const MarkerId("start_point"),
position: _animatedPoints.first,
icon: BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(30, 30)),
"images/start_marker.png",
),
),
if (_animatedPoints.isNotEmpty && _animatedPoints.length == _mockTrackPoints.length)
Marker(
markerId: const MarkerId("end_point"),
position: _animatedPoints.last,
icon: BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(30, 30)),
"images/end_marker.png",
),
),
]),
polylines: {_trackPolyline},
interactive: true,
),
// 搜索框
Positioned(
top: 20,
left: 20,
right: 20,
child: Row(
children: [
Expanded(
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: "搜索兴趣点...",
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
),
onChanged: (value) => _debouncer.run(() => _searchPoi(value)),
),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => _searchPoi(_searchController.text),
child: const Text("搜索"),
),
],
),
),
// 轨迹控制按钮
Positioned(
bottom: 20,
left: 20,
child: ElevatedButton(
onPressed: _toggleTrack,
style: ElevatedButton.styleFrom(
backgroundColor: _isTracking ? Colors.red : Colors.blue,
),
child: Text(_isTracking ? "暂停轨迹" : "开始轨迹"),
),
),
// 定位按钮
Positioned(
bottom: 20,
right: 20,
child: FloatingActionButton(
onPressed: () {
if (_hasLocationPermission) {
_mapController.moveCamera(CameraUpdate.newLatLngZoom(
_mapController.myLocation?.position ?? _initialCenter,
16.0,
));
} else {
_checkLocationPermission();
}
},
child: const Icon(Icons.my_location),
),
),
],
),
);
}
@override
void dispose() {
_searchController.dispose();
_debouncer._timer?.cancel();
_animationController.dispose();
_mapController.dispose();
AMapFlutterLocation().dispose();
super.dispose();
}
}
// POI 搜索服务(复用前文代码)
class AMapPoiSearch {
static const String _apiKey = "你的高德地图 API Key";
static const String _searchUrl = "https://restapi.amap.com/v3/place/text";
static Future<List<PoiSearchResult>> searchByKeyword({
required String keyword,
required LatLng center,
int radius = 5000,
int pageSize = 20,
int pageNum = 1,
}) async {
// 实现代码复用前文...
}
}
5.3 鸿蒙应用配置补充
在 config.json 中配置应用包名、签名信息和权限:
json
{
"app": {
"bundleName": "com.example.map_demo",
"vendor": "example",
"version": {
"code": 1000000,
"name": "1.0.0"
},
"apiVersion": {
"compatible": 9,
"target": 10
}
},
"module": {
"package": "com.example.map_demo",
"name": ".MapDemo",
"mainAbility": "com.example.map_demo.MainAbility",
"deviceType": ["phone", "tablet"],
"reqPermissions": [
{
"name": "ohos.permission.LOCATION",
"reason": "用于地图定位和轨迹记录",
"usedScene": {
"ability": ["com.example.map_demo.MainAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.INTERNET",
"reason": "用于加载地图数据和 POI 搜索",
"usedScene": {
"ability": ["com.example.map_demo.MainAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_EXTERNAL_STORAGE",
"reason": "用于存储离线地图数据",
"usedScene": {
"ability": ["com.example.map_demo.MainAbility"],
"when": "inuse"
}
}
],
"distro": {
"deliveryWithInstall": true,
"moduleName": "map_demo",
"moduleType": "entry",
"installationFree": false
}
}
}
六、问题排查与性能优化
6.1 常见问题及解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 地图加载空白 | 1. API Key 错误或未配置;2. 网络权限未申请;3. 插件版本不兼容 | 1. 检查 API Key 是否正确,确保已启用对应服务;2. 确认 config.json 中添加了网络权限;3. 升级 Flutter 和插件版本,参考插件文档的兼容说明 |
| 定位失败 | 1. 定位权限未申请;2. 设备定位功能未开启;3. 室内定位信号弱 | 1. 动态申请定位权限,引导用户授权;2. 提示用户开启设备定位功能;3. 结合网络定位提升精度 |
| POI 搜索无结果 | 1. 关键词错误;2. 搜索半径过小;3. API Key 无 POI 搜索权限 | 1. 检查关键词格式;2. 扩大搜索半径;3. 在地图开放平台启用 POI 搜索服务 |
| 轨迹绘制卡顿 | 1. 轨迹点数量过多;2. 频繁 setState 刷新界面 | 1. 使用抽稀算法减少轨迹点;2. 优化状态管理,避免不必要的刷新 |
6.2 性能优化技巧
-
地图资源管理:
- 按需加载地图:不在前台时暂停地图渲染,释放资源;
- 优化图标资源:使用矢量图(SVG)替代位图,减少内存占用;
- 及时释放控制器:在
dispose方法中释放地图控制器和定位资源。
-
渲染性能优化:
- 减少标注点数量:批量展示 POI 时,根据地图缩放级别动态加载(缩放级别低时不显示密集标注);
- 避免重叠标注:使用聚类算法(如
flutter_map_marker_cluster插件)合并密集标注点; - 优化轨迹绘制:抽稀轨迹点,避免一次性绘制大量线段。
-
网络优化:
- 缓存地图瓦片:启用离线地图功能,减少网络请求;
- 压缩 POI 搜索结果:只返回必要字段,减少数据传输量;
- 批量请求:合并多个 POI 搜索请求,减少接口调用次数。
七、总结与学习资源推荐
7.1 核心知识点总结
本文围绕鸿蒙 Flutter 地图开发,重点讲解了:
- 高德 / 百度地图 SDK 在鸿蒙 Flutter 应用中的集成步骤(权限配置、SDK 初始化、基础地图展示);
- POI 搜索与标注功能实现(API 调用、结果展示、搜索优化);
- 自定义轨迹图层绘制(轨迹数据模型、动态绘制、动画效果、性能优化);
- 完整项目整合与常见问题排查。
通过本文的实战代码,开发者可以快速落地地图相关功能,并根据实际需求扩展离线地图、地图测距、地理编码等进阶功能。
7.2 学习资源推荐
- 官方文档:
- 第三方资源:
- GitHub 开源项目:
7.3 后续学习方向
- 离线地图功能集成(支持离线瓦片下载、离线 POI 搜索);
- 地图交互高级功能(测距、测面积、地理围栏);
- 多地图平台适配(封装统一接口,支持高德 / 百度 / 腾讯地图动态切换);
- 地图与鸿蒙原生功能结合(如鸿蒙位置服务、导航服务)。
更多推荐






所有评论(0)