PS:最近項目要求,希望在選擇地址的時候能夠仿ele來實現定位效果.因此就去做了一下.不過ele使用高德地圖實現的,我是用百度地圖實現的.沒辦法,公司說用百度那就用百度的吧.個人覺得高德應該更加的精准.但也無所謂反正都是地圖定位+Poi搜索.都差不多.
1.使用LocationClient核心類實現定位
2.使用GeoCoder實現地理編碼和反地理編碼
3.使用PoiSearch實現相關的Poi搜索
4.使用SuggestionSearch實現在線建議查詢
5.ele定位效果的實現
百度地圖定位的相關流程我就不進行介紹了.以前也寫過流程,至於如何創建應用,如何申請什么的,我就不進行介紹了,官方上有關於如何創建應用申請AK的流程,還是非常的詳細的.還是說一下如何實現ele的定位效果吧,可能最后的效果稍微有些偏差,不過大體還是差不多的,主要還是提供一個思路.
1.LocationClient定位核心類
LocationClient是實現地圖定位的核心類,LBS基站定位就是通過使用LocationClient來實現的,具體的使用方式如下:
/** * 設置定位的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