Android仿美團地址選擇

  • 2019 年 10 月 18 日
  • 筆記

最近做了這個功能,分享一下,用的是百度地圖api,和美團外賣的地址選擇介面差不多,也就是可以搜索或者滑動地圖展示地址列表給用戶選擇,看下效果圖先。

          

 

文章重點

1、展示地圖並定位到“我”的位置
2、滑動地圖獲取周邊poi(逆地理編碼)
3、搜索框輸入查詢poi(POI檢索)


 

前言

這裡先提一下,我們要選擇的地址資訊其實是POI(Point of Interest),即“興趣點”。在地理資訊系統中,一個POI可以是一棟房子、一個景點、一個郵筒或者一個公交站等。
百度地圖SDK提供三種類型的POI檢索:城市內檢索、周邊檢索和區域檢索(即矩形區域檢索)。這裡我就不詳細介紹了,具體請查看百度地圖開發文檔(http://lbsyun.baidu.com/index.php?title=androidsdk)。

 

需求分析

我們要實現的功能主要包括兩個操作:滑動地圖和搜索框搜索。

  • 滑動地圖:滑動地圖主要是獲取滑動後地圖中心點坐標,然後獲取poi資訊,但是這裡不能用上面提到的三種POI檢索方式,POI檢索都需要傳入關鍵字(不能為空),而我們僅僅只是滑動地圖,所以需要用另外一種方式:逆地理編碼檢索。使用逆地理編碼檢索時,可以通過檢索結果ReverseGeoCodeResult類的getPoiList()方法獲取傳入位置周圍的POI資訊。
  • 搜索框搜索:這裡就可以使用百度地圖SDK提供的三種POI檢索方式來進行檢索,同時為了方便查看,還可以計算出每個POI和用戶之間的距離。

 

具體實現

一、展示地圖並定位到“我”的位置

1.展示地圖

展示地圖非常簡單,首先需要調用SDKInitializer.initialize()方法來進行初始化操作,它接收一個全局的Context參數,記得初始化操作一定要在setContentView()方法前調用(可以到application中進行初始化),然後調用findViewById()方法獲取MapView實例,最後記得要對MapView進行資源釋放。

2.移動到我的位置

 2.1 獲取我的位置
 首先要確定自己的位置,程式碼如下所示:

public class MainActivity extends AppCompatActivity implements OnGetPoiSearchResultListener {      private MyLocationListener myListener = new MyLocationListener();      public LocationClient mLocationClient = null;      private LocationClientOption option = null;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          initLocation();      }        /**       * 初始化定位相關       */      private void initLocation() {          // 聲明LocationClient類          mLocationClient = new LocationClient(getApplicationContext());          mLocationClient.setLocOption(option);          // 註冊監聽函數          mLocationClient.registerLocationListener(myListener);          mLocationClient.start();      }        /**       * 監聽當前位置       */      public class MyLocationListener extends BDAbstractLocationListener {          @Override          public void onReceiveLocation(BDLocation location) {              //mapView 銷毀後不在處理新接收的位置              if (location == null || mMapView == null) {                  return;              }              if (location.getLocType() == BDLocation.TypeGpsLocation                      || location.getLocType() == BDLocation.TypeNetWorkLocation) {                  Log.e(TAG, "當前“我”的位置:" + location.getAddrStr());                  navigateTo(location);              }          }      }  }

 

可以看到,我們首先創建LocationClient實例,然後調用LocationClient的registerLocationListener()方法來註冊一個定位監聽器,當獲取到位置資訊的時候,就會回調這個定位監聽器。開啟定位很簡單,只需要調用一下LocationClient的start()方法就可以了。
定位的結果會回調到監聽器中,也就是MyLocationListener,在onReceiveLocation()方法中即可通過BDLocation對象獲取相關位置詳細資訊。

註:定位屬於危險許可權,所以要動態許可權申請,記得不要忘記了。

 

2.2 移動到我的位置
獲取到定位後就需要將地圖中心點移動到當前位置,程式碼如下:

    private boolean isFirstLocation = true;      /**       * 根據獲取到的位置在地圖上移動“我”的位置       *       * @param location       */      private void navigateTo(BDLocation location) {          double longitude = location.getLongitude();          double latitude = location.getLatitude();          if (isFirstLocation) {              currentLatLng = new LatLng(latitude, longitude);              MapStatus.Builder builder = new MapStatus.Builder();              MapStatus mapStatus = builder.target(currentLatLng).zoom(17.0f).build();              mBaiduMap.animateMapStatus(MapStatusUpdateFactory                      .newMapStatus(mapStatus));              isFirstLocation = false;          }         //讓“我”顯示在地圖上          MyLocationData.Builder locationBuilder = new MyLocationData.Builder();          locationBuilder.latitude(location.getLatitude());          locationBuilder.longitude(location.getLongitude());          MyLocationData locationData = locationBuilder.build();          mBaiduMap.setMyLocationData(locationData);      }

這裡首先將位置資訊封裝到LatLng對象中,然後調用MapStatusUpdateFactory
的newMapStatus()將LatLng對象傳入,接著返回的MapStatusUpdate對象作為參數傳入到BaiduMap的animateMapStatus()方法中。上述程式碼中還使用了一個變數來防止多次調用animateMapStatus()方法,因為移動地圖只需要在程式第一次定位時調用一次。
同時為了顯示一個當前設備的游標,可以利用MyLocationData.Builder類來實現,如程式碼所示,就可將“我”顯示在地圖上了。

二、滑動地圖獲取poi(逆地理編碼)

1. 逆地理編碼

前面已經提到了,我們這裡滑動地圖需要用到逆地理編碼,也就是反向地理解析,逆地理編碼就是將坐標轉換為詳細的地址資訊,程式碼如下:

    //反向地理解析(含有poi列表)      mGeoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(center));        /**       * 反向地理解析,結果中含有poi資訊,用於剛進入地圖和移動地圖時使用       */      private void initGeoCoder() {          mGeoCoder = GeoCoder.newInstance();          mGeoCoder.setOnGetGeoCodeResultListener(new OnGetGeoCoderResultListener() {              @Override              public void onGetGeoCodeResult(GeoCodeResult geoCodeResult) {                }                @Override              public void onGetReverseGeoCodeResult(ReverseGeoCodeResult reverseGeoCodeResult) {                  if (reverseGeoCodeResult.error.equals(SearchResult.ERRORNO.NO_ERROR)) {                      //獲取poi列表                      if (reverseGeoCodeResult.getPoiList() != null) {                          poiInfoListForGeoCoder = reverseGeoCodeResult.getPoiList();                      }                  } else {                      Toast.makeText(mContext, "該位置範圍內無資訊", Toast.LENGTH_SHORT);                  }              }          });      }

這裡我們首先獲取一個GeoCoder實例,然後註冊監聽器,當有解析結果時便會回調到onGetReverseGeoCodeResult()方法中,而解析結果便有我們需要的poi列表。反向解析只需要調用GeoCoder的reverseGeoCode()方法並傳入移動後地圖的中心坐標點即可。

 

2. 監聽地圖滑動

百度地圖提供了一個地圖狀態改變的監聽器,當雙擊、滑動、縮放等操作時便進行回調,如下:

        mBaiduMap.setOnMapStatusChangeListener(new BaiduMap.OnMapStatusChangeListener() {                /**               * 手勢操作地圖,設置地圖狀態等操作導致地圖狀態開始改變。               * @param mapStatus 地圖狀態改變開始時的地圖狀態               */              @Override              public void onMapStatusChangeStart(MapStatus mapStatus) {              }                /** 因某種操作導致地圖狀態開始改變。               * @param mapStatus 地圖狀態改變開始時的地圖狀態               * @param i 取值有:               * 1:用戶手勢觸發導致的地圖狀態改變,比如雙擊、拖拽、滑動底圖               * 2:SDK導致的地圖狀態改變, 比如點擊縮放控制項、指南針圖標               * 3:開發者調用,導致的地圖狀態改變               */              @Override              public void onMapStatusChangeStart(MapStatus mapStatus, int i) {                  Log.e(TAG, "地圖狀態改變開始時:" + i + "");              }                /**               * 地圖狀態變化中               * @param mapStatus 當前地圖狀態               */              @Override              public void onMapStatusChange(MapStatus mapStatus) {                  LatLng latlng = mBaiduMap.getMapStatus().target;                  addMarker(latlng);              }                /**               * 地圖狀態改變結束               * @param mapStatus 地圖狀態改變結束後的地圖狀態               */              @Override              public void onMapStatusChangeFinish(MapStatus mapStatus) {                  center = mBaiduMap.getMapStatus().target;                  //反向地理解析(含有poi列表)                  mGeoCoder.reverseGeoCode(new ReverseGeoCodeOption()                          .location(center));              }          });

如上,當地圖從滑動到結束會回調4個方法,我們需要用到的是:地圖狀態變化中和地圖狀態改變結束,也就是對應地圖滑動中和滑動結束時。
滑動結束:當滑動結束時便調用反向地理解析出結果,這個上面已經說了。
滑動中:我們會發現當我們滑動地圖時,地圖上會有一個圖標始終處於地圖中心,這裡就是利用地圖狀態變化中這個回調來添加一個marker,也就是在地圖上添加一個圖標,不過這個方法一次滑動可能會回調很多次,但是如果只在滑動結束後添加,用戶體驗不好,所以如果實在要考慮性能的話可以換個思路,將圖標固定在螢幕上大致地圖的中心,這樣滑動地圖看起來也一樣的。
添加marker的方法就不詳解了,源碼里有,一看就懂了。

三、搜索框輸入查詢poi(POI檢索)

搜索框搜索也就是使用關鍵字檢索POI資訊,這裡不要和Sug檢索弄混了,Sug(Suggestion POI search)檢索是根據部分關鍵字檢索出可能的完整關鍵字名稱,即關鍵字匹配。而POI檢索是根據關鍵字檢索符合的POI具體資訊。
上面說過POI檢索有三種方式,這裡結合我們的需求來說,使用城市內檢索更加合適,也就是傳入城市和關鍵字進行查詢,當然你也可以使用另外兩種檢索方式,步驟如下:

1. 創建POI檢索實例

mPoiSearch = PoiSearch.newInstance();

 

2. 創建POI檢索監聽器

OnGetPoiSearchResultListener listener = new OnGetPoiSearchResultListener() {      /**       * 獲取POI搜索結果       * @param poiResult Poi檢索結果,包括城市檢索,周邊檢索,區域檢索       */      @Override      public void onGetPoiResult(PoiResult poiResult) {          if (poiResult.error == SearchResult.ERRORNO.NO_ERROR) {              poiInfoListForSearch = poiResult.getAllPoi();//POI集合          }            if (poiResult.error == SearchResult.ERRORNO.AMBIGUOUS_KEYWORD) {              // 當輸入關鍵字在本市沒有找到,但在其他城市找到時,返回包含該關鍵字資訊的城市列表              String strInfo = "在";              for (CityInfo cityInfo : poiResult.getSuggestCityList()) {                  strInfo += cityInfo.city;                  strInfo += ",";              }              strInfo += "找到結果";              Toast.makeText(mContext, strInfo, Toast.LENGTH_LONG).show();          }      }      @Override      public void onGetPoiDetailResult(PoiDetailSearchResult poiDetailSearchResult) {        }      @Override      public void onGetPoiIndoorResult(PoiIndoorResult poiIndoorResult) {        }      //廢棄      @Override      public void onGetPoiDetailResult(PoiDetailResult poiDetailResult) {        }  };

 

3. 設置檢索監聽器

mPoiSearch.setOnGetPoiSearchResultListener(listener);

 

4. 發起檢索請求

mPoiSearch.searchInCity((new PoiCitySearchOption())          .city(cityName)//城市名稱          .keyword(keyword)//必填          .pageCapacity(pageSize)//每頁條數          .pageNum(loadIndex));//分頁頁碼

 

5. 釋放檢索實例

mPoiSearch.destroy();

為了方便用戶查看,我們可以在列表中展示每一個poi和用戶之間的距離,利用DistanceUtil類的getDistance()方法傳入兩個點坐標的LatLng對象即可計算,如下:

double distance=DistanceUtil.getDistance(currentLatLng, latLng);

 

最後利用EditText的addTextChangedListener監聽器監聽輸入框,如果值改變就進行檢索。


 

至此,整個功能也就做完了,demo里沒有做列表分頁和動態許可權申請,這個常用的你們就自個加咯,最後放下demo地址:
GitHub:https://github.com/yangxch/BaiDuMapSelectDemo

 

原創不易,轉載請註明出處!