PS:最近项目要求,但愿在选择地址的时候可以仿ele来实现定位效果.所以就去作了一下.不过ele使用高德地图实现的,我是用百度地图实现的.没办法,公司说用百度那就用百度的吧.我的以为高德应该更加的精准.但也无所谓反正都是地图定位+Poi搜索.都差很少.java
1.使用LocationClient核心类实现定位android
2.使用GeoCoder实现地理编码和反地理编码git
3.使用PoiSearch实现相关的Poi搜索api
4.使用SuggestionSearch实如今线建议查询网络
5.ele定位效果的实现异步
百度地图定位的相关流程我就不进行介绍了.之前也写过流程,至于如何建立应用,如何申请什么的,我就不进行介绍了,官方上有关于如何建立应用申请AK的流程,仍是很是的详细的.仍是说一下如何实现ele的定位效果吧,可能最后的效果稍微有些误差,不过大致仍是差很少的,主要仍是提供一个思路.ide
1.LocationClient定位核心类布局
LocationClient是实现地图定位的核心类,LBS基站定位就是经过使用LocationClient来实现的,具体的使用方式以下: gradle
/** * 设置定位的option * */ mLocClient = new LocationClient(this); //实例化LocationClient mLocClient.registerLocationListener(new MyLocationListener()); //注册定位监听 LocationClientOption option = new LocationClientOption(); option.setOpenGps(true); // 打开gps option.setCoorType("bd09ll"); // 设置坐标类型 option.setScanSpan(1000); // 设置查询范围,默认500 mLocClient.setLocOption(option); // 设置option mLocClient.start();
LocationClient实例化以后,须要设置相应的LocationOption,也就是定位的一些选项,经过指定的设置,决定以怎样的形式实现定位,好比说定位的时候须要打开gps,设置坐标的类型,以及搜索的范围等等.同时须要设置相关的监听.优化
/** * LBS定位监听 * */ public class MyLocationListener implements BDLocationListener { @Override public void onReceiveLocation(BDLocation location) { // map view 销毁后不在处理新接收的位置 if (IsFirstLoc) { IsFirstLoc = false; point = new LatLng(location.getLatitude(), location.getLongitude()); geoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(point)); /** * 设置当前位置为定位到的地理位置 * */ MapStatus.Builder builder = new MapStatus.Builder(); builder.target(point).zoom(20.0f); baiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(builder.build())); } } }
设置完相关的监听以后就能够在LocationClient完成定位后去作一些其余的事情,好比说在地图上显示定位到的当前位置,获取到定位的坐标,坐标的形式是以经度和纬度的组合形式,获取到定位到的坐标后,咱们就能够根据坐标实现地理编码和反地理编码.
2.使用GeoCoder实现地理编码与反编码
GeoCoder的使用方式仍是很是简单的,只须要实例化对象,而后设置监听回调就能够了..
地理编码:将当前的位置信息转化成坐标的形式(经度+纬度)
geoCoder = GeoCoder.newInstance(); //GeoCoder对象的实例化 geoCoder.setOnGetGeoCodeResultListener(this); //设置监听 //须要实现的方法: @Override public void onGetGeoCodeResult(GeoCodeResult geoCodeResult) { //这里通常不作其余处理 }
反地理编码:将咱们当前的坐标信息转化成物理位置.须要额外注意:反地理编码须要在网络状态链接良好的状况下才可以实现
geoCoder = GeoCoder.newInstance(); //GeoCoder对象的实例化 geoCoder.setOnGetGeoCodeResultListener(this); //设置监听 //须要实现的方法: @Override public void onGetReverseGeoCodeResult(ReverseGeoCodeResult reverseGeoCodeResult) { /** * 获取反地理编码后的城市信息,街道信息,Poi信息等 * */ currentCity = reverseGeoCodeResult.getAddress(); }
ReverseGeoCodeResult类的具体形式
package com.baidu.mapapi.search.geocode; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable.Creator; import com.baidu.mapapi.model.LatLng; import com.baidu.mapapi.search.core.PoiInfo; import com.baidu.mapapi.search.core.SearchResult; import com.baidu.mapapi.search.core.SearchResult.ERRORNO; import com.baidu.mapapi.search.geocode.c; import com.baidu.mapapi.search.geocode.d; import java.util.List; public class ReverseGeoCodeResult extends SearchResult { private String a; private String b; private ReverseGeoCodeResult.AddressComponent c; private LatLng d; private List<PoiInfo> e; public static final Creator<ReverseGeoCodeResult> CREATOR = new c(); ReverseGeoCodeResult() { } ReverseGeoCodeResult(ERRORNO var1) { super(var1); } protected ReverseGeoCodeResult(Parcel var1) { super(var1); this.a = var1.readString(); this.b = var1.readString(); this.c = (ReverseGeoCodeResult.AddressComponent)var1.readParcelable(ReverseGeoCodeResult.AddressComponent.class.getClassLoader()); this.d = (LatLng)var1.readValue(LatLng.class.getClassLoader()); this.e = var1.createTypedArrayList(PoiInfo.CREATOR); } public String getBusinessCircle() { return this.a; } void a(String var1) { this.a = var1; } public String getAddress() { return this.b; } void b(String var1) { this.b = var1; } public ReverseGeoCodeResult.AddressComponent getAddressDetail() { return this.c; } void a(ReverseGeoCodeResult.AddressComponent var1) { this.c = var1; } public LatLng getLocation() { return this.d; } void a(LatLng var1) { this.d = var1; } public List<PoiInfo> getPoiList() { return this.e; } void a(List<PoiInfo> var1) { this.e = var1; } public int describeContents() { return 0; } public void writeToParcel(Parcel var1, int var2) { super.writeToParcel(var1, var2); var1.writeString(this.a); var1.writeString(this.b); var1.writeParcelable(this.c, 0); var1.writeValue(this.d); var1.writeTypedList(this.e); } public static class AddressComponent implements Parcelable { public String streetNumber; public String street; public String district; public String city; public String province; public static final Creator<ReverseGeoCodeResult.AddressComponent> CREATOR = new d(); public int describeContents() { return 0; } public void writeToParcel(Parcel var1, int var2) { var1.writeString(this.streetNumber); var1.writeString(this.street); var1.writeString(this.district); var1.writeString(this.city); var1.writeString(this.province); } public AddressComponent() { } protected AddressComponent(Parcel var1) { this.streetNumber = var1.readString(); this.street = var1.readString(); this.district = var1.readString(); this.city = var1.readString(); this.province = var1.readString(); } } }
反地理编码结束以后,咱们就能够拿到一些相关信息,好比说咱们当前位置所在的省市,城市,区域,街道,以及街道号,以及附近的一些Poi等等.这些均可以经过反地理编码的结束后的回调拿到相关的信息.这里咱们的反地理编码只是获取了相关的城市信息.为了后续的在线建议查询作准备.
3.使用PoiSearch实现相关的Poi搜索
Poi:poi中文翻译为兴趣点.其实就是周边的一些ktv,酒店,餐馆,理发店等等都是一个poi.在实现了基础定位的前提后,去搜索附近的poi.这样就能够完成一些其余事情.好比说订一份外卖,预约一个房间等等.这些都是基于poi搜索才可以实现的.
Poi搜索有三种不一样的方式,周边搜索,区域搜索,城市内搜索.这里我只说周边搜索,由于ele应该也是使用的周边搜索.
三种搜索方式代码上其实大致相同,只是搜索的方式不大同样而已.大致分为三个步骤,首先是对象的实例化,而后设置PoiNearBySearchOption,最后设置监听回调便可.
/** * 房子Poi数据搜索 * @注意: 全部的Poi搜索都是异步完成的 * */ private void nearByAllPoiSearch() { allpoiSearch = PoiSearch.newInstance(); allPoiData.clear(); allpoiSearch.setOnGetPoiSearchResultListener(new OnGetPoiSearchResultListener() { @Override public void onGetPoiResult(PoiResult poiResult) { if (poiResult.getAllPoi() == null) { Toast.makeText(getApplicationContext(),"定位失败,暂无数据信息",Toast.LENGTH_LONG).show(); } else { allPoiData.addAll(poiResult.getAllPoi()); } } @Override public void onGetPoiDetailResult(PoiDetailResult poiDetailResult) { //Poi详情数据 } @Override public void onGetPoiIndoorResult(PoiIndoorResult poiIndoorResult) { //室内Poi数据 } }); /** * 设置Poi Option * 当前搜索为附近搜索:以圆形的状态进行搜索 * 还有两种其余的搜素方式:范围搜素和城市内搜索 * */ PoiNearbySearchOption nearbySearchOption = new PoiNearbySearchOption(); nearbySearchOption.location(new LatLng(point.latitude, point.longitude)); //设置坐标 nearbySearchOption.keyword("房子"); //设置关键字 nearbySearchOption.radius(2000); //搜索范围的半径 nearbySearchOption.pageCapacity(15); //设置最多容许加载的poi数量,默认10 allpoiSearch.searchNearby(nearbySearchOption); }
这里我设置了Poi容许加载的数量,默认是10条数据,也就是说,若是咱们不设置分页,百度只会给咱们返回10条Poi数据信息,这是默认的状况,这里我修改了容许加载的Poi数量,也就是15个Poi数据信息,若是你们不想写分页,那么能够设置这个属性,按照本身的方式去指定加载多少条数据.
4.使用SuggestionSearch实如今线建议查询
在线建议查询:根据城市和关键字搜索出相应的位置信息(模糊查询)使用起来仍是很是的简单的.只须要实例化对象,设置结果回调就能够根据咱们输入的关键字搜索相关的地理位置信息.
/** * 在线建议查询对象实例化+设置监听 * @在线建议查询: 根据城市和关键字搜索出相应的位置信息(模糊查询) * */ keyWordsPoiSearch = SuggestionSearch.newInstance(); keyWordsPoiSearch.setOnGetSuggestionResultListener(new OnGetSuggestionResultListener() { @Override public void onGetSuggestionResult(SuggestionResult suggestionResult) { keyWordPoiData.clear(); if (suggestionResult.getAllSuggestions() == null) { Toast.makeText(getApplicationContext(),"暂无数据信息",Toast.LENGTH_LONG).show(); } else { keyWordPoiData = suggestionResult.getAllSuggestions(); //设置Adapter结束 suggestAdapter = new SuggestAddressAdapter(getApplicationContext(), keyWordPoiData); inputPoiListView.setAdapter(suggestAdapter); } } }); keyWordsPoiSearch.requestSuggestion((new SuggestionSearchOption()).keyword(location_name.getText().toString()).city(currentCity));
大致的东西基本就介绍完了.仍是来分析一下ele是如何实现的定位效果吧.我是按照个人思路去实现的,可能会有一些与其并非特别的同样.总之仍是提供一个大致的思路才是关键.咱们先看一下效果图
咱们看着这个效果图来讲,上层是一个搜索框和一个MapView.而且中间位置有一个ImageView.下层是一个ViewIndicator,我这个ViewIndicator并无自定义View,只是一个布局,所以实现起来可能没有那么的优雅,你们能够选择去优化这里,而后下面是一个ViewPager来实现4个Fragment的切换.
须要说明的就是中间这个ImageView,我在这里标识了它并非地图上的Marker.而是直接定义了一个FrameLayout,让这个ImageView粘在了这个中间位置,咱们在移动地图的时候,咱们看着好像是这个ImageView也在移动,其实只是这个MapView在移动而已,这个ImageView实际是一直保持不动的.那么移动的时候咱们明显看到数据发生了变化,这里只是每次在移动的时候都获取MapView的中心点坐标,由于ImageView是始终在中心显示的,所以每次取得就是中心坐标,而后再进行Poi搜素就能够了.这样就能够发现Fragment里的数据会发生明显的变化.
这里我第一次定位和Poi搜索都是在MainActivity里面完成的,而后将数据经过setArguments()传递过去.Fragment在首次加载的时候只须要getArguments()来获取相应的数据,而后在本身的ListView当中设置adapter就能够第一次直接显示数据了,同时Fragment在onAttach到Activity的时候须要注册一个广播,这个广播的用来接收,当地图状态发生改变的时候,也就是咱们平移了地图,MainActivity须要告知Fragment地图状态已经发生了变化,须要更新Poi数据了.那么Fragment在接收到这条广播的时候,就知道地图状态已经改变,须要根据当前的中心点坐标搜索出如今的Poi数据,而后经过adapter.notifyDataSetChanged来更新ListView里面的数据便可.
/** * 第一次加载的时候由Activity搜索,将数据传递给Fragment,后续由Fragment来完成搜索功能 * */ Bundle allPoiBundle = new Bundle(); allPoiBundle.putParcelableArrayList("allPoiData", (ArrayList<? extends Parcelable>) allPoiData); allPoiFragment.setArguments(allPoiBundle); //获取数据,只须要在OnCreateView获取 @Nullable @Override public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_all, null); allPoiSearch = PoiSearch.newInstance(); allData.clear(); allData = getArguments().getParcelableArrayList("allPoiData"); initView(view); return view; }
这里咱们能够看到我只在MainActivity搜索了一次Poi,若是咱们每次都在MainActivity中搜索完Poi而后再传递Fragment的话,这样其实会耗费大量的时间,数据更新的速度也比较的慢,你们能够去试试每次在MainActivity中定位完数据以后再发送个Fragment.看看这样实现是否优雅.其实咱们也能够彻底不在MainActivity中搜索Poi,彻底交给Fragment其实也是能够的.
这里还须要说一下Fragment的一个数据预加载问题,咱们都知道Fragment是有预加载机制的,默认状况下Fragment只会预加载一页数据,所以这里我改变了它的预加载数量.
/** * 这里修改了Fragment的预加载数量,一次加载三页数据,默认是一页 * */ viewPager.setOffscreenPageLimit(3);
改变这个也是有缘由的,由于咱们须要在OnAttach时为Fragment注册一个广播,监听地图状态是否发生了变化,若是使用默认的加载机制,好比说咱们如今就在所有这个Fragment页面,那么只有所有和写字楼注册了这个广播,其余两个页面尚未OnAttach到Activity上,这时咱们改变地图状态,前面这两页数据会发生明显变化,然后面的两页是根本不知道数据已经变化了.同理同样.若是咱们在最后一页,前两页已经被销毁,已经onDetach()Activity了,那么这两页也是拿不到数据的.所以我这里改变了它的预加载数量.不管如何滑动,都可以接收到数据.使用默认的预加载机制,出现问题的主要缘由其实和Fragment的生命周期有紧密关联的.有空我会去写一篇关于Fragment的生命周期的博客.如今咱们只须要知道就能够了.
最后就是这个蛋疼的搜索框,浪费了我至关长的时间.按照ele的效果来看,当点击搜索框的时候,须要弹出一个页面覆盖掉这个页面,而后根据关键字搜索出数据,以列表项的形式展示出来,其实这里就使用到了在下建议查询,根据咱们输入的关键字搜素出相应的数据信息.可是这里蛋疼的问题在于当咱们点击返回的键的时候不是结束这个Activity,而是返回到地图这个页面.所以这里我这里只能重写onKeyDown事件,而后拦截Back事件.若是搜索框中的EditText在获取焦点状态的状况下,点击返回键的话,那么返回地图页面,一直不结束这个Activity,可是蛋疼的事就来了,当返回到这个MapView页面的时候,搜索框中的EditText仍然优先获取到了焦点,不管咱们怎么点击返回键,这个Activity都不会被销毁.这样就有很大的问题.最后我找到了一种方法,只能让整个搜索框这个布局去抢占EditText的焦点.那么在返回地图的时候,EditText就永远不会优先获取到焦点事件了.并且还可以正常销毁Activity.
/** * 监听onKeyDown事件 * 目的是判断当前页面是地图显示页面仍是在线建议查询页面 * */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (isFocus) { inputPoiSearchLayout.setVisibility(View.GONE); location_name.setText(""); location_name.clearFocus(); keyWordPoiData.clear(); layout.setFocusable(true); layout.setFocusableInTouchMode(true); layout.requestFocus(); isFocus = false; return true; } } return super.onKeyDown(keyCode, event); }
该说的也就这么多了,用上的知识点,以及如何具体实现.最后放上一个源代码.还有一些须要注意的就是若是使用Android Studio开发.而且jar包都保存在libs文件夹中的话,在build.gradle中别忘了配置.
sourceSets { main { jniLibs.srcDir 'libs' //这里必需要有,不然会报.so文件的异常 } instrumentTest.setRoot('tests') debug.setRoot('build-types/debug') release.setRoot('build-types/release') }
源代码(连接) http://pan.baidu.com/s/1mhMQumc