flutter 最近有在地图上选择地址的需求,要求如下
1.移动地图获取根据地图中心点获取周边的poi信息
2.搜索,根据搜索内容提示相关地点信息,点击移动到相关位置,显示出该位置周边的poi信息
废话少说,先上视频
flutter百度地图选址
百度地图的集成这个就不说了,直接参考官网集成即可,咱们直接看实现代码
先看布局
布局比较简单,就是上面两个按钮,下面是固定高度的搜索框和列表,有一些我封装的内容,大家可以自行绘制,主要看与地图相关的内容即可
return Column(
children: [
Expanded(
child: Stack(
children: [
BMFMapWidget(
onBMFMapCreated: (controller) {
viewModel.mapController = controller;
onBMFMapCreated();
},
mapOptions: viewModel.mapOptions,
),
Container(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 20),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0x80a6a6a6),
//white,
transparent,
],
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextView(
"取消",
textSize: 15,
textColor: Colors.white,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 5,
paddingBottom: 5,
onClick: () {
Get.back();
},
),
TextView(
"确定",
backgroundColor: green,
cornerRadius: 2,
textSize: 15,
textColor: Colors.white,
paddingLeft: 10,
paddingTop: 5,
paddingBottom: 5,
paddingRight: 10,
onClick: () {
if (viewModel.poiChooseName.isEmpty) {
showToast("您还未选择任何地址");
} else{
Get.back(result: viewModel.poiInfo);
}
},
)
],
),
),
Center(
child: ImageView(
"assets/images/ic_map_location.png",
width: 30,
height: 30,
)),
],
)),
MyContainer(
height: viewModel.contentHieght,
backgroundColor: Colors.white,
cornerRadius: 10,
child: Column(
children: [
const SizedBox(
height: 10,
),
CommonSearchLayout(viewModel.searchController),
Expanded(
child: MyListView(
viewModel.isSearch
? viewModel.suggestList
: viewModel.poiList,
dividerHeight: 1,
dividerColor: dividerColor, (context, index) {
if (viewModel.isSearch) {
//搜索
return SuggestItem(viewModel.suggestList[index], index,
(index1) {
onSuggestChoose(index1);
});
} else {
//选择
return PoiItem(
viewModel.poiList[index], viewModel.poiChooseName,
(text) {
viewModel.poiChooseName = text;
viewModel.poiInfo = viewModel.poiList[index];
notifyChanges();
});
}
}),
)
],
),
)
],
);
展现出来的样式
注意地图创建后调用的发方法onBMFMapCreated(),当地图创建之后,判断地图是否确实是被加载好了,加载好了之后就调用一个获取用户位置的方法,把用户当前定位挪到地图中心去,然后设置地图监听,也就是当地区的中心区域有变化的时候,重新获取地图中心点周边的poi信息
void onBMFMapCreated() {
//地图被创建
viewModel.mapController.setMapDidLoadCallback(callback: () {
//地图已加载,地图中心移动到用户所在的位置
showUserLocation();
});
//监听地图区域变化
viewModel.mapController.setMapRegionDidChangeWithReasonCallback(callback:
(BMFMapStatus mapStatus, BMFRegionChangeReason regionChangeReason) {
print("地图区域变化:${mapStatus.targetGeoPt!.latitude}");
viewModel.poiChooseName = "";
//根据地图中心点检索周边事物
getPoiByLocation(
mapStatus.targetGeoPt!.latitude, mapStatus.targetGeoPt!.longitude);
notifyChanges();
});
}
获取用户位置并且把用户位置移动到中央,LocationUtils().getCurrentLocation()是我自己封装的获取用户当前位置的方法,调用的是百度地图官方的sdk的方法,自己封装了一下,这块大家可以自己来写
//获取用户位置并移动地图到人员中心
void showUserLocation() {
LocationUtils().getCurrentLocation(context, (info) {
viewModel.mapOptions = BMFMapOptions(
center: BMFCoordinate(info.latitude!, info.longitude!),
zoomLevel: viewModel.mapLevel,
);
viewModel.mapController.updateMapOptions(viewModel.mapOptions);
getPoiByLocation(info.latitude!, info.longitude!);
});
}
然后重点来了getPoiByLocation(),这个方法很重要,这个方法是干什么的呢,这个方法就是根据坐标获取周边poi信息的方法,本质上就是坐标转地址(反地理编码),他会给出很多这个坐标周边的poi信息,这个周边的poi信息就是咱们最关键最需要的信息
这里面相应的位置点的名称、地址、坐标什么的就都获取到了,List<BMFPoiInfo>? list,这个就是获取到的poi信息的列表,里面我做了一些小处理,根据搜索的内容把转化的poi信息与之相符的放在第一位,这个列表获取到了,后续怎么处理根据需求自行处理即可
//反地理编码,根据坐标查询poi信息
void getPoiByLocation(double latitude, double longitude) async {
BMFReverseGeoCodeSearchOption reverseGeoCodeSearchOption =
BMFReverseGeoCodeSearchOption(
location: BMFCoordinate(latitude, longitude));
// 检索实例
BMFReverseGeoCodeSearch reverseGeoCodeSearch = BMFReverseGeoCodeSearch();
// 逆地理编码回调
reverseGeoCodeSearch.onGetReverseGeoCodeSearchResult(callback:
(BMFReverseGeoCodeSearchResult result, BMFSearchErrorCode errorCode) {
List<BMFPoiInfo>? list = result.poiList;
viewModel.poiList.clear();
if (result.poiList!.isNotEmpty) {
viewModel.poiList.addAll(result.poiList!);
if (viewModel.poiChooseName.isNotEmpty) {
late BMFPoiInfo info;
for (int i = 0; i < viewModel.poiList.length; i++) {
if (viewModel.poiList[i].name == viewModel.poiChooseName) {
viewModel.poiInfo = viewModel.poiList[i];
viewModel.poiList.removeAt(i);
viewModel.poiList.insert(0, viewModel.poiInfo);
}
}
}
notifyChanges();
for (int i = 0; i < list!.length; i++) {
BMFPoiInfo info = list[i];
print("poi信息:${info.name!}---${info.address!}");
print("poi信息:${info.pt!.longitude}---${info.pt!.latitude}");
}
} else {
print("poi信息:空");
}
//print(`逆地理编码 errorCode = ${errorCode} \n result = ${result?.toMap()}`);
// 解析reslut,具体参考demo
});
await reverseGeoCodeSearch.reverseGeoCodeSearch(reverseGeoCodeSearchOption);
}
再然后就是搜索了,因为我搜索和现实位置的时候用的是同一个listview,所以我加了一个状态的判断
//当前是否是搜索的状态
bool isSearch = false;
以此来判断当前是显示poi信息,还是显示的搜索结果,监听输入框,当输入文字有变化的时候,搜索内容,关键方法searchLocation(),cityname给一个就行,不给会报错,是可以进行全国范围内搜索的,这个点不要怕
//根据输入的内容搜索相应的位置
void searchLocation() async {
viewModel.suggestList.clear();
BMFSuggestionSearchOption suggestionSearchOption =
BMFSuggestionSearchOption(
keyword: viewModel.searchController.text, cityname: '北京市');
BMFSuggestionSearch suggestionSearch = BMFSuggestionSearch();
suggestionSearch.onGetSuggestSearchResult(callback:
(BMFSuggestionSearchResult result, BMFSearchErrorCode errorCode) {
if (result.suggestionList!.isNotEmpty) {
//有搜索结果
viewModel.suggestList.addAll(result.suggestionList!);
for (int i = 0; i < result.suggestionList!.length; i++) {
print("提醒位置:${result.suggestionList![i].key}");
}
} else {
print("提醒位置:空");
}
notifyChanges();
});
await suggestionSearch.suggestionSearch(suggestionSearchOption);
}
当选择了搜索的位置之后,就需要把地图移动到相应的位置,然后重新获取次位置周边的poi信息
,主要方法onSuggestChoose(),当搜索提示的位置选择之后需要做的处理
void onSuggestChoose(int index) {
//选中此项
viewModel.suggestInfo = viewModel.suggestList[index];
viewModel.poiChooseName = viewModel.suggestInfo.key!;
//位置移动到此处
viewModel.mapOptions = BMFMapOptions(
center: BMFCoordinate(viewModel.suggestInfo.location!.latitude,
viewModel.suggestInfo.location!.longitude),
//zoomLevel: viewModel.mapLevel,
);
viewModel.mapController.updateMapOptions(viewModel.mapOptions);
//关闭键盘
SystemChannels.textInput.invokeMethod('TextInput.hide');
//搜索周边poi
getPoiByLocation(viewModel.suggestInfo.location!.latitude,
viewModel.suggestInfo.location!.longitude);
//搜索状态改为选择状态
viewModel.isSearch = false;
notifyChanges();
}
核心代码基本上就是这样,其中一些组件是我封装的,无非也就是一些textfield,text,listview之类的,整体是封装了state,使用的是get_it这个库进行状态管理
下面我把完整的代码贴上来,大家参考一下
主要页面,大家的布局直接放在build里面就行,我外面进行了一次封装
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:get/get.dart';
import 'package:status_bar_control/status_bar_control.dart';
import 'package:zuguantong/activity/map/choose/item_poi.dart';
import 'package:zuguantong/activity/map/choose/item_suggest.dart';
import 'package:zuguantong/activity/map/choose/mapchoose_view_model.dart';
import 'package:zuguantong/yuchuangbase/const/colors.dart';
import 'package:zuguantong/yuchuangbase/utils/location_utils.dart';
import 'package:zuguantong/yuchuangbase/widget/ImageView.dart';
import 'package:zuguantong/yuchuangbase/widget/MyContainer.dart';
import 'package:zuguantong/yuchuangbase/widget/MyListView.dart';
import 'package:zuguantong/yuchuangbase/widget/TextView.dart';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import '../../../yuchuangbase/base/base_state.dart';
import '../../../yuchuangbase/widget/CommonSearchLayout.dart';
///地图选址
class MapChooseActivity extends StatefulWidget {
var keyboardVisibilityController = KeyboardVisibilityController();
MapChooseActivity({Key? key}) : super(key: key);
@override
State<MapChooseActivity> createState() => _MapChooseActivityState();
}
class _MapChooseActivityState
extends BaseState<MapChooseActivity, MapChooseViewModel> {
@override
void onCreate() async {
hideTitle();
showContent();
viewModel.searchController.addListener(() {
print("内容变化:开始");
if (viewModel.searchController.text.isEmpty) {
viewModel.isSearch = false;
notifyChanges();
} else{
viewModel.isSearch = true;
searchLocation();
}
});
//await StatusBarControl.setHidden(true, animation:StatusBarAnimation.NONE);
widget.keyboardVisibilityController.onChange.listen((bool visible) {
print('键盘: $visible');
if (visible) {
viewModel.isSearch = true;
} else {
viewModel.isSearch = false;
}
notifyChanges();
});
}
@override
void dispose() {
super.dispose();
SystemChannels.textInput.invokeMethod('TextInput.hide');
}
@override
getContent(BuildContext context) {
return Column(
children: [
Expanded(
child: Stack(
children: [
BMFMapWidget(
onBMFMapCreated: (controller) {
viewModel.mapController = controller;
onBMFMapCreated();
},
mapOptions: viewModel.mapOptions,
),
Container(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 20),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0x80a6a6a6),
//white,
transparent,
],
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextView(
"取消",
textSize: 15,
textColor: Colors.white,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 5,
paddingBottom: 5,
onClick: () {
Get.back();
},
),
TextView(
"确定",
backgroundColor: green,
cornerRadius: 2,
textSize: 15,
textColor: Colors.white,
paddingLeft: 10,
paddingTop: 5,
paddingBottom: 5,
paddingRight: 10,
onClick: () {
if (viewModel.poiChooseName.isEmpty) {
showToast("您还未选择任何地址");
} else{
Get.back(result: viewModel.poiInfo);
}
},
)
],
),
),
Center(
child: ImageView(
"assets/images/ic_map_location.png",
width: 30,
height: 30,
)),
],
)),
MyContainer(
height: viewModel.contentHieght,
backgroundColor: Colors.white,
cornerRadius: 10,
child: Column(
children: [
const SizedBox(
height: 10,
),
CommonSearchLayout(viewModel.searchController),
Expanded(
child: MyListView(
viewModel.isSearch
? viewModel.suggestList
: viewModel.poiList,
dividerHeight: 1,
dividerColor: dividerColor, (context, index) {
if (viewModel.isSearch) {
//搜索
return SuggestItem(viewModel.suggestList[index], index,
(index1) {
onSuggestChoose(index1);
});
} else {
//选择
return PoiItem(
viewModel.poiList[index], viewModel.poiChooseName,
(text) {
viewModel.poiChooseName = text;
viewModel.poiInfo = viewModel.poiList[index];
notifyChanges();
});
}
}),
)
],
),
)
],
);
}
void onBMFMapCreated() {
//地图被创建
viewModel.mapController.setMapDidLoadCallback(callback: () {
//地图已加载,地图中心移动到用户所在的位置
showUserLocation();
});
//监听地图区域变化
viewModel.mapController.setMapRegionDidChangeWithReasonCallback(callback:
(BMFMapStatus mapStatus, BMFRegionChangeReason regionChangeReason) {
print("地图区域变化:${mapStatus.targetGeoPt!.latitude}");
viewModel.poiChooseName = "";
//根据地图中心点检索周边事物
getPoiByLocation(
mapStatus.targetGeoPt!.latitude, mapStatus.targetGeoPt!.longitude);
notifyChanges();
});
}
//获取用户位置并移动地图到人员中心
void showUserLocation() {
LocationUtils().getCurrentLocation(context, (info) {
viewModel.mapOptions = BMFMapOptions(
center: BMFCoordinate(info.latitude!, info.longitude!),
zoomLevel: viewModel.mapLevel,
);
viewModel.mapController.updateMapOptions(viewModel.mapOptions);
getPoiByLocation(info.latitude!, info.longitude!);
});
}
void getPoiByLocation(double latitude, double longitude) async {
BMFReverseGeoCodeSearchOption reverseGeoCodeSearchOption =
BMFReverseGeoCodeSearchOption(
location: BMFCoordinate(latitude, longitude));
// 检索实例
BMFReverseGeoCodeSearch reverseGeoCodeSearch = BMFReverseGeoCodeSearch();
// 逆地理编码回调
reverseGeoCodeSearch.onGetReverseGeoCodeSearchResult(callback:
(BMFReverseGeoCodeSearchResult result, BMFSearchErrorCode errorCode) {
List<BMFPoiInfo>? list = result.poiList;
viewModel.poiList.clear();
if (result.poiList!.isNotEmpty) {
viewModel.poiList.addAll(result.poiList!);
if (viewModel.poiChooseName.isNotEmpty) {
late BMFPoiInfo info;
for (int i = 0; i < viewModel.poiList.length; i++) {
if (viewModel.poiList[i].name == viewModel.poiChooseName) {
viewModel.poiInfo = viewModel.poiList[i];
viewModel.poiList.removeAt(i);
viewModel.poiList.insert(0, viewModel.poiInfo);
}
}
}
notifyChanges();
for (int i = 0; i < list!.length; i++) {
BMFPoiInfo info = list[i];
print("poi信息:${info.name!}---${info.address!}");
print("poi信息:${info.pt!.longitude}---${info.pt!.latitude}");
}
} else {
print("poi信息:空");
}
//print(`逆地理编码 errorCode = ${errorCode} \n result = ${result?.toMap()}`);
// 解析reslut,具体参考demo
});
await reverseGeoCodeSearch.reverseGeoCodeSearch(reverseGeoCodeSearchOption);
}
//根据输入的内容搜索相应的位置
void searchLocation() async {
viewModel.suggestList.clear();
BMFSuggestionSearchOption suggestionSearchOption =
BMFSuggestionSearchOption(
keyword: viewModel.searchController.text, cityname: '北京市');
BMFSuggestionSearch suggestionSearch = BMFSuggestionSearch();
suggestionSearch.onGetSuggestSearchResult(callback:
(BMFSuggestionSearchResult result, BMFSearchErrorCode errorCode) {
if (result.suggestionList!.isNotEmpty) {
//有搜索结果
viewModel.suggestList.addAll(result.suggestionList!);
for (int i = 0; i < result.suggestionList!.length; i++) {
print("提醒位置:${result.suggestionList![i].key}");
}
} else {
print("提醒位置:空");
}
notifyChanges();
});
await suggestionSearch.suggestionSearch(suggestionSearchOption);
}
void onSuggestChoose(int index) {
//选中此项
viewModel.suggestInfo = viewModel.suggestList[index];
viewModel.poiChooseName = viewModel.suggestInfo.key!;
//位置移动到此处
viewModel.mapOptions = BMFMapOptions(
center: BMFCoordinate(viewModel.suggestInfo.location!.latitude,
viewModel.suggestInfo.location!.longitude),
//zoomLevel: viewModel.mapLevel,
);
viewModel.mapController.updateMapOptions(viewModel.mapOptions);
//关闭键盘
SystemChannels.textInput.invokeMethod('TextInput.hide');
//搜索周边poi
getPoiByLocation(viewModel.suggestInfo.location!.latitude,
viewModel.suggestInfo.location!.longitude);
//搜索状态改为选择状态
viewModel.isSearch = false;
notifyChanges();
}
}
状态管理自定义的变量
import 'package:flutter/cupertino.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';
import 'package:zuguantong/yuchuangbase/base/base_view_model.dart';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
class MapChooseViewModel extends BaseViewModel {
//搜索textfield的控制
TextEditingController searchController = TextEditingController();
//poi信息列表
List<BMFPoiInfo> poiList = [];
//搜索提示列表
List<BMFSuggestionInfo> suggestList = [];
//选中的搜索提示信息
late BMFSuggestionInfo suggestInfo;
//地图缩放等级
int mapLevel = 12;
//选中的位置信息
late BMFPoiInfo poiInfo;
//当前是否是搜索的状态
bool isSearch = false;
//临时选中的位置名称
String poiChooseName = "";
//地图默认的坐标(北京天安门)
double lat = 39.917215;
double lon = 116.380341;
String x = "徐工225H摊铺机";
//下面白色区域的高度
double contentHieght = 350;
//地图控制器
late BMFMapController mapController;
//地图初始化时候的Options
late BMFMapOptions mapOptions = BMFMapOptions(
center: BMFCoordinate(lat, lon),
zoomLevel: 12,
mapPadding: BMFEdgeInsets(left: 30, top: 0, right: 30, bottom: 0));
}
主要就是里面的几个核心的方法,我都抽离出来单独讲了,大家有问题可以留言交流,有错欢迎指正,感谢!!!