
-
百学须先立志---学前须知:
我们经常在各大主流 APP 上要求被写上地址,如百度外卖、爱鲜蜂收货地址等等;其中他们大多数是可以让我们在地图上移动选址。就如下面这段 GIF 演示的一样:
-
尽信书,不如无书---能学到什么?
1、地图状态 MapStatus 类及监听 setOnMapStatusChangeListener 2、定位 LocationClient 类 3、反地理编码 GeoCoder 类
-
工欲善其事必先利其器---申请 Key
百度地图访问应用(AK)申请地址:http://lbsyun.baidu.com/apiconsole/key
如果你是第一次申请的话可以参看我的另一篇教程:Android 中级篇之百度地图 SDK v3.5.0-申请密钥详解[AndroidStudio 下获取 SHA1]
-
兵马未动,粮草先行---导入百度地图 jar 包
1、 进入百度地图 API-首页
2、鼠标移动到 开发 标签页上,选择 Android 地图 SDK
3、选择 相关下载
4、选择 全部下载 (此时这里会跳转到新的下载页面)
5、选择 相对应的开发包(本教程大家选择和下图标注的一样即可)
6、导入 jar 包到工程
如果你是第一次导入的话可以参看我另一篇教程:Android 中级篇之百度地图 SDK v3.5.0-配置环境及发布[图解 AndroidStudio 下配置.so 文件] 这里呢就不再赘述了。
-
权限及服务---AndroidManifest
-
配置.so 文件及其他---build.gradle
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.0" defaultConfig { applicationId "com.scp" minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { //代码混淆 minifyEnabled false //zip 优化 zipAlignEnabled true //移除无用的 resource 文件 shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } sourceSets { main { jniLibs.srcDirs = ['libs'] } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:appcompat-v7:23.0.0' compile files('libs/BaiduLBS_Android.jar') }
-
主布局---activity_main.xml
首先我们先分析一下,整个布局结构上面是百度的 MapView (地图区域)下面是一个 ListView(选址列表区域) ,乍一看好像是如下图描述一样:
从布局中我们可以看到 MapView 占了 250dp(大家自己可以随意给个值,不要太小即可)。下面的 ListView则是填充了剩余的空间。
-
第一步:基础地图
public class MainActivity extends AppCompatActivity { private MapView mMapView; private BaiduMap mBaiduMap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //在使用 SDK 各组件之前初始化 context 信息,传入 ApplicationContext //注意该方法要再 setContentView 方法之前实现 SDKInitializer.initialize(getApplicationContext()); //requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initView(); } private void initView() { mMapView = (MapView) findViewById(R.id.main_bdmap); mBaiduMap = mMapView.getMap(); } @Override protected void onResume() { super.onResume(); // activity 恢复时同时恢复地图控件 mMapView.onResume(); } @Override protected void onPause() { super.onPause(); // activity 暂停时同时暂停地图控件 mMapView.onPause(); } @Override protected void onDestroy() { super.onDestroy(); // activity 销毁时同时销毁地图控件 mMapView.onDestroy(); mMapView = null; } }
这里我们的 MainActivity 继承的是 AppCompatActivity,继承 Activity 也行。
//在使用 SDK 各组件之前初始化 context 信息,传入 ApplicationContext //注意该方法要再 setContentView 方法之前实现 SDKInitializer.initialize(getApplicationContext());
这段代码一段要放在 setContentView(R.layout.activity_main); 之前。继续往下看,其中有一句代码requestWindowFeature(Window.FEATURE_NO_TITLE);被注释掉了,需要去掉标题栏的朋友可以加上这句代码。下面呢还有另外一种方法去掉标题栏,打开我们的 style.xml 。
改成 Theme.AppCompat.Light.NoActionBar
运行效果图:
-
第二步:定位
继续书写我们的 MainActivity 里面的代码:
``` public class MainActivity extends AppCompatActivity implements BDLocationListener, OnGetGeoCoderResultListener, BaiduMap.OnMapStatusChangeListener {
private MapView mMapView; private BaiduMap mBaiduMap; private ListView poisLL; /** * 定位模式 */ private MyLocationConfiguration.LocationMode mCurrentMode; /** * 定位端 */ private LocationClient mLocClient; /** * 是否是第一次定位 */ private boolean isFirstLoc = true; /** * 定位坐标 */ private LatLng locationLatLng; /** * 定位城市 */ private String city; /** * 反地理编码 */ private GeoCoder geoCoder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //在使用 SDK 各组件之前初始化 context 信息,传入 ApplicationContext //注意该方法要再 setContentView 方法之前实现 SDKInitializer.initialize(getApplicationContext()); //requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initView(); } private void initView() { mMapView = (MapView) findViewById(R.id.main_bdmap); mBaiduMap = mMapView.getMap(); poisLL = (ListView) findViewById(R.id.main_pois); //定义地图状态 MapStatus mMapStatus = new MapStatus.Builder().zoom(18).build(); MapStatusUpdate mMapStatusUpdate = MapStatusUpdateFactory.newMapStatus(mMapStatus); //改变地图状态 mBaiduMap.setMapStatus(mMapStatusUpdate); //地图状态改变相关监听 mBaiduMap.setOnMapStatusChangeListener(this); //开启定位图层 mBaiduMap.setMyLocationEnabled(true); //定位图层显示方式 mCurrentMode = MyLocationConfiguration.LocationMode.NORMAL; /** * 设置定位图层配置信息,只有先允许定位图层后设置定位图层配置信息才会生效 * customMarker 用户自定义定位图标 * enableDirection 是否允许显示方向信息 * locationMode 定位图层显示方式 */ mBaiduMap.setMyLocationConfigeration(new MyLocationConfiguration(mCurrentMode, true, null)); //初始化定位 mLocClient = new LocationClient(this); //注册定位监听 mLocClient.registerLocationListener(this); //定位选项 LocationClientOption option = new LocationClientOption(); /** * coorType - 取值有 3 个: * 返回国测局经纬度坐标系:gcj02 * 返回百度墨卡托坐标系 :bd09 * 返回百度经纬度坐标系 :bd09ll */ option.setCoorType("bd09ll"); //设置是否需要地址信息,默认为无地址 option.setIsNeedAddress(true); //设置是否需要返回位置语义化信息,可以在 BDLocation.getLocationDescribe()中得到数据,ex:"在天安门附近", 可以用作地址信息的补充 option.setIsNeedLocationDescribe(true); //设置是否需要返回位置 POI 信息,可以在 BDLocation.getPoiList()中得到数据 option.setIsNeedLocationPoiList(true); /** * 设置定位模式 * Battery_Saving * 低功耗模式 * Device_Sensors * 仅设备(Gps)模式 * Hight_Accuracy * 高精度模式 */ option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); //设置是否打开 gps 进行定位 option.setOpenGps(true); //设置扫描间隔,单位是毫秒 当<1000(1s)时,定时定位无效 option.setScanSpan(1000); //设置 LocationClientOption mLocClient.setLocOption(option); //开始定位 mLocClient.start();
} /** * 定位监听 * * @param bdLocation */ @Override public void onReceiveLocation(BDLocation bdLocation) { //如果 bdLocation 为空或 mapView 销毁后不再处理新数据接收的位置 if (bdLocation == null || mBaiduMap == null) { return; } //定位数据 MyLocationData data = new MyLocationData.Builder() //定位精度 bdLocation.getRadius() .accuracy(bdLocation.getRadius()) //此处设置开发者获取到的方向信息,顺时针 0-360 .direction(bdLocation.getDirection()) //经度 .latitude(bdLocation.getLatitude()) //纬度 .longitude(bdLocation.getLongitude()) //构建 .build(); //设置定位数据 mBaiduMap.setMyLocationData(data); //是否是第一次定位 if (isFirstLoc) { isFirstLoc = false; LatLng ll = new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude()); MapStatusUpdate msu = MapStatusUpdateFactory.newLatLngZoom(ll, 18); mBaiduMap.animateMapStatus(msu); } //获取坐标,待会用于 POI 信息点与定位的距离 locationLatLng = new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude()); //获取城市,待会用于 POISearch city = bdLocation.getCity(); //创建 GeoCoder 实例对象 geoCoder = GeoCoder.newInstance(); //发起反地理编码请求(经纬度->地址信息) ReverseGeoCodeOption reverseGeoCodeOption = new ReverseGeoCodeOption(); //设置反地理编码位置坐标 reverseGeoCodeOption.location(new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude())); geoCoder.reverseGeoCode(reverseGeoCodeOption); //设置查询结果监听者 geoCoder.setOnGetGeoCodeResultListener(this); } //地理编码查询结果回调函数 @Override public void onGetGeoCodeResult(GeoCodeResult geoCodeResult) { } //反地理编码查询结果回调函数 @Override public void onGetReverseGeoCodeResult(ReverseGeoCodeResult reverseGeoCodeResult) { ListpoiInfos = reverseGeoCodeResult.getPoiList(); PoiAdapter poiAdapter = new PoiAdapter(MainActivity.this, poiInfos); poisLL.setAdapter(poiAdapter); } /** * 手势操作地图,设置地图状态等操作导致地图状态开始改变 * * @param mapStatus 地图状态改变开始时的地图状态 */ @Override public void onMapStatusChangeStart(MapStatus mapStatus) { } /** * 地图状态变化中 * * @param mapStatus 当前地图状态 */ @Override public void onMapStatusChange(MapStatus mapStatus) { } /** * 地图状态改变结束 * * @param mapStatus 地图状态改变结束后的地图状态 */ @Override public void onMapStatusChangeFinish(MapStatus mapStatus) { //地图操作的中心点 LatLng cenpt = mapStatus.target; geoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(cenpt)); } //回退键 @Override public void onBackPressed() { finish(); } @Override protected void onResume() { super.onResume(); // activity 恢复时同时恢复地图控件 mMapView.onResume(); } @Override protected void onPause() { super.onPause(); // activity 暂停时同时暂停地图控件 mMapView.onPause(); } @Override protected void onDestroy() { super.onDestroy(); //退出时停止定位 mLocClient.stop(); //退出时关闭定位图层 mBaiduMap.setMyLocationEnabled(false); // activity 销毁时同时销毁地图控件 mMapView.onDestroy(); //释放资源 if (geoCoder != null) { geoCoder.destroy(); } mMapView = null; } } ``` 代码分段分析:  这里我们放大了地图,zoom(18)【地图缩放级别 3~20】,接下来对我们的定位选项做一个简单的说明:  当我们定位完了之后,我们就可以对定位好的数据进行处理了,简单说明一下我们的定位监听做了哪些事情:  定位好了之后我们进行过反地理编码,下面说明一下反地理编码监听里面做了哪些事情: 
-
PoiAdapter
这里会用到一张图 baidumap_ico_poi_on.png 大家右键另存为就行了:
适配器视图 locationpois.xml :
布局简单说明:
实现类 PoiAdapter :
``` public class PoiAdapter extends BaseAdapter {
private Context context; private Listpois; private LinearLayout linearLayout;
PoiAdapter(Context context, Listpois) { this.context = context; this.pois = pois; } @Override public int getCount() { return pois.size(); } @Override public Object getItem(int position) { return pois.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.locationpois_item, null); linearLayout = (LinearLayout) convertView.findViewById(R.id.locationpois_linearlayout); holder = new ViewHolder(convertView); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } if (position == 0 && linearLayout.getChildCount() < 2) { ImageView imageView = new ImageView(context); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(32, 32); imageView.setLayoutParams(params); imageView.setBackgroundColor(Color.TRANSPARENT); imageView.setImageResource(R.mipmap.baidumap_ico_poi_on); imageView.setScaleType(ImageView.ScaleType.FIT_XY); linearLayout.addView(imageView, 0, params); holder.locationpoi_name.setTextColor(Color.parseColor("#FF5722")); } PoiInfo poiInfo = pois.get(position); holder.locationpoi_name.setText(poiInfo.name); holder.locationpoi_address.setText(poiInfo.address); return convertView; } class ViewHolder { TextView locationpoi_name; TextView locationpoi_address; ViewHolder(View view) { locationpoi_name = (TextView) view.findViewById(R.id.locationpois_name); locationpoi_address = (TextView) view.findViewById(R.id.locationpois_address); } } } ``` 代码分段分析: 
-
地图状态变化---OnMapStatusChangeListener
来看看我们现在运行是什么样子的:
大家移动一下地图试试。
-
第三步:添加定位图标
更改 activity_main.xml 布局文件:
此次没有任何实现代码添加或者改动,运行看一下效果:
大家移动一下地图试试。
-
输入关键字显示相关地址列表
首先我们先更改 activity_main.xml :
```
``` 代码说明:  接下来书写适配器的 item 布局 **poisearch_item.xml**: ``` ``` 代码说明:  最终更改我们的 **MainActivity** 里面的代码 **(请结合下面代码说明来看)** : ``` public class MainActivity extends AppCompatActivity implements BDLocationListener, OnGetGeoCoderResultListener, BaiduMap.OnMapStatusChangeListener, TextWatcher { private MapView mMapView; private BaiduMap mBaiduMap; private ListView poisLL; /** * 定位模式 */ private MyLocationConfiguration.LocationMode mCurrentMode; /** * 定位端 */ private LocationClient mLocClient; /** * 是否是第一次定位 */ private boolean isFirstLoc = true; /** * 定位坐标 */ private LatLng locationLatLng; /** * 定位城市 */ private String city; /** * 反地理编码 */ private GeoCoder geoCoder; /** * 界面上方布局 */ private RelativeLayout topRL; /** * 搜索地址输入框 */ private EditText searchAddress; /** * 搜索输入框对应的 ListView */ private ListView searchPois; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //在使用 SDK 各组件之前初始化 context 信息,传入 ApplicationContext //注意该方法要再 setContentView 方法之前实现 SDKInitializer.initialize(getApplicationContext()); //requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initView(); } private void initView() { mMapView = (MapView) findViewById(R.id.main_bdmap); mBaiduMap = mMapView.getMap(); poisLL = (ListView) findViewById(R.id.main_pois); topRL = (RelativeLayout) findViewById(R.id.main_top_RL); searchAddress = (EditText) findViewById(R.id.main_search_address); searchPois = (ListView) findViewById(R.id.main_search_pois); //定义地图状态 MapStatus mMapStatus = new MapStatus.Builder().zoom(18).build(); MapStatusUpdate mMapStatusUpdate = MapStatusUpdateFactory.newMapStatus(mMapStatus); //改变地图状态 mBaiduMap.setMapStatus(mMapStatusUpdate); //地图状态改变相关监听 mBaiduMap.setOnMapStatusChangeListener(this); //开启定位图层 mBaiduMap.setMyLocationEnabled(true); //定位图层显示方式 mCurrentMode = MyLocationConfiguration.LocationMode.NORMAL; /** * 设置定位图层配置信息,只有先允许定位图层后设置定位图层配置信息才会生效 * customMarker 用户自定义定位图标 * enableDirection 是否允许显示方向信息 * locationMode 定位图层显示方式 */ mBaiduMap.setMyLocationConfigeration(new MyLocationConfiguration(mCurrentMode, true, null)); //初始化定位 mLocClient = new LocationClient(this); //注册定位监听 mLocClient.registerLocationListener(this); //定位选项 LocationClientOption option = new LocationClientOption(); /** * coorType - 取值有 3 个: * 返回国测局经纬度坐标系:gcj02 * 返回百度墨卡托坐标系 :bd09 * 返回百度经纬度坐标系 :bd09ll */ option.setCoorType("bd09ll"); //设置是否需要地址信息,默认为无地址 option.setIsNeedAddress(true); //设置是否需要返回位置语义化信息,可以在 BDLocation.getLocationDescribe()中得到数据,ex:"在天安门附近", 可以用作地址信息的补充 option.setIsNeedLocationDescribe(true); //设置是否需要返回位置 POI 信息,可以在 BDLocation.getPoiList()中得到数据 option.setIsNeedLocationPoiList(true); /** * 设置定位模式 * Battery_Saving * 低功耗模式 * Device_Sensors * 仅设备(Gps)模式 * Hight_Accuracy * 高精度模式 */ option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); //设置是否打开 gps 进行定位 option.setOpenGps(true); //设置扫描间隔,单位是毫秒 当<1000(1s)时,定时定位无效 option.setScanSpan(1000); //设置 LocationClientOption mLocClient.setLocOption(option); //开始定位 mLocClient.start(); } /** * 定位监听 * * @param bdLocation */ @Override public void onReceiveLocation(BDLocation bdLocation) { //如果 bdLocation 为空或 mapView 销毁后不再处理新数据接收的位置 if (bdLocation == null || mBaiduMap == null) { return; } //定位数据 MyLocationData data = new MyLocationData.Builder() //定位精度 bdLocation.getRadius() .accuracy(bdLocation.getRadius()) //此处设置开发者获取到的方向信息,顺时针 0-360 .direction(bdLocation.getDirection()) //经度 .latitude(bdLocation.getLatitude()) //纬度 .longitude(bdLocation.getLongitude()) //构建 .build(); //设置定位数据 mBaiduMap.setMyLocationData(data); //是否是第一次定位 if (isFirstLoc) { isFirstLoc = false; LatLng ll = new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude()); MapStatusUpdate msu = MapStatusUpdateFactory.newLatLngZoom(ll, 18); mBaiduMap.animateMapStatus(msu); } //获取坐标,待会用于 POI 信息点与定位的距离 locationLatLng = new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude()); //获取城市,待会用于 POISearch city = bdLocation.getCity(); //文本输入框改变监听,必须在定位完成之后 searchAddress.addTextChangedListener(this); //创建 GeoCoder 实例对象 geoCoder = GeoCoder.newInstance(); //发起反地理编码请求(经纬度->地址信息) ReverseGeoCodeOption reverseGeoCodeOption = new ReverseGeoCodeOption(); //设置反地理编码位置坐标 reverseGeoCodeOption.location(new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude())); geoCoder.reverseGeoCode(reverseGeoCodeOption); //设置查询结果监听者 geoCoder.setOnGetGeoCodeResultListener(this); } //地理编码查询结果回调函数 @Override public void onGetGeoCodeResult(GeoCodeResult geoCodeResult) { } //反地理编码查询结果回调函数 @Override public void onGetReverseGeoCodeResult(ReverseGeoCodeResult reverseGeoCodeResult) { ListpoiInfos = reverseGeoCodeResult.getPoiList(); PoiAdapter poiAdapter = new PoiAdapter(MainActivity.this, poiInfos); poisLL.setAdapter(poiAdapter); } /** * 手势操作地图,设置地图状态等操作导致地图状态开始改变 * * @param mapStatus 地图状态改变开始时的地图状态 */ @Override public void onMapStatusChangeStart(MapStatus mapStatus) { } /** * 地图状态变化中 * * @param mapStatus 当前地图状态 */ @Override public void onMapStatusChange(MapStatus mapStatus) { } /** * 地图状态改变结束 * * @param mapStatus 地图状态改变结束后的地图状态 */ @Override public void onMapStatusChangeFinish(MapStatus mapStatus) { //地图操作的中心点 LatLng cenpt = mapStatus.target; geoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(cenpt)); } /** * 输入框监听---输入之前 * * @param s 字符序列 * @param start 开始 * @param count 总计 * @param after 之后 */ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } /** * 输入框监听---正在输入 * * @param s 字符序列 * @param start 开始 * @param before 之前 * @param count 总计 */ @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } /** * 输入框监听---输入完毕 * * @param s */ @Override public void afterTextChanged(Editable s) { if (s.length() == 0 || "".equals(s.toString())) { searchPois.setVisibility(View.GONE); } else { //创建 PoiSearch 实例 PoiSearch poiSearch = PoiSearch.newInstance(); //城市内检索 PoiCitySearchOption poiCitySearchOption = new PoiCitySearchOption(); //关键字 poiCitySearchOption.keyword(s.toString()); //城市 poiCitySearchOption.city(city); //设置每页容量,默认为每页 10 条 poiCitySearchOption.pageCapacity(10); //分页编号 poiCitySearchOption.pageNum(1); poiSearch.searchInCity(poiCitySearchOption); //设置 poi 检索监听者 poiSearch.setOnGetPoiSearchResultListener(new OnGetPoiSearchResultListener() { //poi 查询结果回调 @Override public void onGetPoiResult(PoiResult poiResult) { ListpoiInfos = poiResult.getAllPoi(); PoiSearchAdapter poiSearchAdapter = new PoiSearchAdapter(MainActivity.this, poiInfos, locationLatLng); searchPois.setVisibility(View.VISIBLE); searchPois.setAdapter(poiSearchAdapter); } //poi 详情查询结果回调 @Override public void onGetPoiDetailResult(PoiDetailResult poiDetailResult) { } }); } } //回退键 @Override public void onBackPressed() { finish(); } @Override protected void onResume() { super.onResume(); // activity 恢复时同时恢复地图控件 mMapView.onResume(); } @Override protected void onPause() { super.onPause(); // activity 暂停时同时暂停地图控件 mMapView.onPause(); } @Override protected void onDestroy() { super.onDestroy(); //退出时停止定位 mLocClient.stop(); //退出时关闭定位图层 mBaiduMap.setMyLocationEnabled(false); // activity 销毁时同时销毁地图控件 mMapView.onDestroy(); //释放资源 if (geoCoder != null) { geoCoder.destroy(); } mMapView = null; } } ``` 代码说明:  最终运行效果与文章开篇展示效果一样,这里呢就不再重复贴图了。
-
GitHub
最终项目 GitHub 地址:https://github.com/scp504677840/MoveMapLocation.git