Android之仿ele地圖定位效果

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

相關文章
相關標籤/搜索