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? 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? 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 createState() => _MapChooseActivityState();

}

class _MapChooseActivityState

extends BaseState {

@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? 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 poiList = [];

//搜索提示列表

List 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));

}

主要就是里面的几个核心的方法,我都抽离出来单独讲了,大家有问题可以留言交流,有错欢迎指正,感谢!!!

查看原文