使用過百度地圖的同學知道,它有個街景功能,可以看到許多地方的實景。這裏就其街景內容的實現,進行下學習。
在百度地圖SDK的官網上可以看到,百度對開發者提供了很多相關的內容,方便我們進行學習。關於SDK的使用方法,包括jar包導入,*.so 動態庫的添加位置及AndroidManifest文件的配置不做爲我們這裏討論的內容,官方文檔已經介紹的很詳細,不做無聊的搬運工。
效果圖
這裏我們首先預覽下,今天最終要實現的效果圖
如圖所示,我們這裏的實現,就是兩個頁面的內容,一個是基礎的地圖MapView,一個是街景地圖PanoView。接下來,就這兩個頁面(Activity)分別展開來說。(由於GIF圖片大小限制,效果不是很理想,文章結尾有源碼地址,可以自己跑一下看一下效果先)
地圖MapView實現
地圖MapView的簡單顯示
佈局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.baidu.mapapi.map.MapView
android:id="@+id/bmapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true" />
</LinearLayout>
Application
一般情況下,我們的應用程序都會有一個繼承自Application的類,用於實現一些初始化的方法,這裏可以在Application裏執行一些百度地圖初始化的工作,這也是官方提倡的方式。
public void onCreate() {
super.onCreate();
mInstance = this; SDKInitializer.initialize(getApplicationContext());
}
Activity
在Activity的OnCreate方法中實現
setContentView(R.layout.activity_mapview);
mMapView = (MapView) findViewById(R.id.bmapView);
上面這樣一段簡單的代碼,就可以在Activity中顯示出一個MapView,也就是我們最熟悉的地圖頁面,是不是很簡單,就像我們顯示一個TextView一樣。
這裏說明寫一下,按照官方的API指導文檔,使用MapView等百度地圖SDK所提供的各種實現,是需要去申請相關的key的,申請的方法在官網有着詳細的介紹,這裏就不再粘貼複製了;很多同學在使用MapView的時候發現,程序運行後地圖沒有顯示,顯示的都是一些方格子,這往往是由於key沒有申請,或申請的方式不當造成的
MapView顯示到當前位置
每次打開百度地圖,都會自動定位到我們當前所在的位置,或者是我們搜索某個特定的地方作爲新的位置,整個地圖所呈現的區域都是新位置周邊的環境。這裏,關於地圖的定位和搜索的相關實現內容,就不展開來說,不當做此次的重點。
假設我們已通過定位(或者是搜索),定位了到了一個位置
**
* 假設我們當前的位置在此
*/
private final double latitude = 39.963175;
private final double longitude = 116.400244;
這個位置按照新聞裏常聽到的說法就是,東經116.40度,北緯39.96度,位於北京市東城區舊鼓樓大街丙1號。
接下來,我們要做的就是將MapView的視圖更新到我們“定位”的位置,這個位置周邊的地圖纔是我們關心的。
mBaiduMap = mMapView.getMap();
//定義Maker座標點
point = new LatLng(latitude, longitude);
//定義地圖狀態
final MapStatus mMapStatus = new MapStatus.Builder()
.target(point)
.zoom(18)
.build();
//定義MapStatusUpdate對象,以便描述地圖狀態將要發生的變化
MapStatusUpdate mMapStatusUpdate = MapStatusUpdateFactory.newMapStatus(mMapStatus);
//改變地圖狀態
mBaiduMap.setMapStatus(mMapStatusUpdate);
這裏的mBaiduMap 是一個BaiduMap的實例,通過MapView的getMap方法即可獲得。我們對地圖的各種操作,設置屬性都是基於這個實例進行。
通過上面的代碼,我們就可以將MapView的視圖更新到我們所想要的位置了。
添加View到MapView
添加Marker
按照百度地圖API的說法,我們添加到地圖上的小圖標統一稱爲Marker。
//構建Marker圖標
bitmap = BitmapDescriptorFactory
.fromResource(R.drawable.icon_markc);
//構建MarkerOption,用於在地圖上添加Marker
option = new MarkerOptions()
.position(point)
.icon(bitmap);
//在地圖上添加Marker,並顯示
mBaiduMap.addOverlay(option);
通過上面的實現,我們就可以將一個小圖標添加到地圖層,作爲標記。我們日常使用地圖時,所搜周邊後呈現的一系列小圓點就是如此(如下圖)
ShowInfoWindow使用
最後一步,實現顯示街景縮略圖的那個小彈框。
這裏首先自定義一下我們要添加到地圖層的View。
view = LayoutInflater.from(mContext).inflate(R.layout.pano_overlay, null);
pic = (ImageView) view.findViewById(R.id.panoImageView);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(mContext, PanoDemoMain.class);
intent.putExtra("latitude", latitude);
intent.putExtra("longitude", longitude);
startActivity(intent);
}
});
這裏pic這個ImageView用於顯示我們要展示的街景縮略圖。pano_overlay是整個彈框的佈局,很簡單,這裏就不貼代碼了。
同時,我們爲這個自定義View設置點擊事件,方便我們跳轉到PanoView街景地圖頁面,並且將當前位置傳遞過去。
由於祖國地大物博,所以街景的覆蓋並非百分之百,所以說,不是每個地方都有街景可以顯示,有些鳥不拉屎的地方是看不到的。那我們怎麼知道什麼地方有街景呢?API爲我們提供了很好的檢測方法
new Thread(new Runnable() {
@Override
public void run() {
PanoramaRequest request = PanoramaRequest.getInstance(mContext);
BaiduPanoData locationPanoData = request.getPanoramaInfoByLatLon(longitude, latitude);
//開發者可以判斷是否有外景(街景)
if (locationPanoData.hasStreetPano()) {
String url = baseUrl + locationPanoData.getPid();
Message message = new Message();
message.what = 0x01;
message.obj = url;
handler.sendMessage(message);
}
}
}).start();
這樣,我們就可以根據當前位置,先檢測一下是否有街景可以顯示。這裏,如果當前位置有街景,我們就通過Handler通知主線程去更新UI
private class myHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x01) {
String url = (String) msg.obj;
Glide.with(mContext).load(url).into(pic);
InfoWindow mInfoWindow = new InfoWindow(view, point, -57);
//顯示InfoWindow
mBaiduMap.showInfoWindow(mInfoWindow);
}
}
}
這裏看一下,InfoWindow的說明及其構造函數
public class InfoWindow extends java.lang.Object
在地圖中顯示一個信息窗口,可以設置一個View作爲該窗口的內容,也可以設置一個 BitmapDescriptor 作爲該窗口的內容。
public InfoWindow(View view, LatLng position, int yOffset)
/**
通過傳入的 view 構造一個 InfoWindow, 此時只是利用該view
生成一個Bitmap繪製在地圖中,監聽事件由開發者實現。
Parameters:
view - InfoWindow 展示的 view
position - InfoWindow 顯示的地理位置
yOffset - InfoWindow Y 軸偏移量
*/
在Handler的handleMessage方法中,我們通過返回的url加載圖片,並將自定義的彈框View顯示到之前一步添加的marker偏上一點的地方(這就是InfoWindow的構造函數中-57的意義)
關於這個加載圖片的URL,可以參考這裏靜態圖API。
這樣,就實現了MapView頁面所有的內容。通過點擊InfoWindow,就可以跳轉到PanoView所在的界面去查看街景地圖。
接下來,我們將介紹PanoView街景地圖的實現。
街景地圖PanoViewActivity實現
街景地圖PanoView基礎
街景地圖PanoView的顯示和基礎地圖MapView十分相似
首先是在佈局文件中定義view
<com.baidu.lbsapi.panoramaview.PanoramaView
android:id="@+id/panorama"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:visibility="visible" />
在Activity的OnCreate方法中
PanoDemoApplication app = (PanoDemoApplication) this.getApplication();
if (app.mBMapManager == null) {
app.mBMapManager = new BMapManager(app);
app.mBMapManager.init(new PanoDemoApplication.MyGeneralListener());
}
mPanoView = (PanoramaView) findViewById(R.id.panorama);
mPanoView.setPanorama(longitude, latitude);
這裏同樣需要的是在Application類中做一些初始化工作;對我們所使用key的有效性進行檢測。
public void initEngineManager(Context context) {
if (mBMapManager == null) {
mBMapManager = new BMapManager(context);
}
if (!mBMapManager.init(new MyGeneralListener())) {
Toast.makeText(PanoDemoApplication.getInstance().getApplicationContext(), "BMapManager 初始化錯誤!",
Toast.LENGTH_LONG).show();
}
}
// 常用事件監聽,用來處理通常的網絡錯誤,授權驗證錯誤等
static class MyGeneralListener implements MKGeneralListener {
@Override
public void onGetPermissionState(int iError) {
// 非零值表示key驗證未通過
if (iError != 0) {
// 授權Key錯誤:
Toast.makeText(PanoDemoApplication.getInstance().getApplicationContext(),
"請在AndoridManifest.xml中輸入正確的授權Key,並檢查您的網絡連接是否正常!error: " + iError, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(PanoDemoApplication.getInstance().getApplicationContext(), "key認證成功", Toast.LENGTH_LONG)
.show();
}
}
}
同時在Activity裏也需要做一些初始化的工作,最後就是通過PanoView的setPanorama()方法實現街景的顯示。
關於這裏用到的setPanorama(),根據API我們可以看到
public void setPanorama(java.lang.String pid)
//根據全景pid值切換全景場景
public void setPanorama(int x,int y)
//根據百度墨卡託投影座標切換全景場景
public void setPanorama(double longitude,double latitude)
//根據百度經緯度座標切換全景場景
public void setPanoramaByUid(java.lang.String uid,
int panoType)
//根據uid值切換全景場景
也就是說,不僅通過經緯度,而且可以通過別的方式實現街景地圖的功能。這裏我們就使用了大家最熟悉的經緯度,對於別的實現方式有興趣的同學,可以自己去探索一下。
將地圖MapView展示在街景PanoView上面
如圖所示,將一個MapView顯示在PanoView之上;很自然的我們會寫出下面的佈局方式:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.baidu.lbsapi.panoramaview.PanoramaView
android:id="@+id/panorama"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:visibility="visible" />
<LinearLayout
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:background="#00ffffff">
<com.baidu.mapapi.map.MapView
android:id="@+id/bmapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:padding="20dp" />
</LinearLayout>
</RelativeLayout>
這樣,我們在整個PanoView的左下角定義一個60x60大小的view用於顯示一個MapView。
實現對街景視圖操作的監聽
街景SDK爲我們提供了PanoramaViewListener這個接口,可以實現對從街景視圖開始繪製到完成繪製,對街景地圖的操作(如點擊,旋轉)的監聽。
這裏我們重點看一下onMessage(String msgName, int msgType)這個回調方法。
public void onMessage(String msgName, int msgType) {
Log.e(LTAG, "msgName--->" + msgName + ", msgType--->" + msgType);
switch (msgType) {
case 8213:
//旋轉
Log.e(PanoViewActivity.class.getSimpleName(), "now,the heading is " + mPanoView.getPanoramaHeading());
Message message = new Message();
message.what = ACTION_DRAG;
message.arg1 = (int) mPanoView.getPanoramaHeading();
handler.sendMessage(message);
break;
case 12302:
//點擊
Log.e(PanoViewActivity.class.getSimpleName(), "clicked");
Message msg = new Message();
msg.what = ACTION_CLICK;
handler.sendMessage(msg);
break;
default:
break;
}
}
這裏不得不吐槽一下,官方所提供的API文檔,對這個onMessage回調方法中的參數居然沒有任何有價值的解釋。這裏的8213及12302完全是通過打印日誌自己總結出的規律。
這樣,我們對於不同的操作,就可以通過Handler實現不同的UI效果。我們看一下handler的實現:
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case ACTION_CLICK:
if (titleVisible) {
titleVisible = false;
textTitle.startAnimation(animationHide);
textTitle.setVisibility(View.GONE);
sv.setVisibility(View.GONE);
} else {
titleVisible = true;
textTitle.startAnimation(animationShow);
textTitle.setVisibility(View.VISIBLE);
sv.setVisibility(View.VISIBLE);
}
break;
case ACTION_DRAG:
float heading = (float) msg.arg1;
mBaiduMap.clear();
//構建MarkerOption,用於在地圖上添加Marker
option = new MarkerOptions()
.position(point)
.rotate(360-heading)
.icon(bitmap);
//在地圖上添加Marker,並顯示
mBaiduMap.addOverlay(option);
break;
default:
break;
}
}
}
這裏的處理就分兩種情況:
- 點擊事件
我們仿照百度地圖的樣式,實現標題欄及MapView的隱藏,並添加動畫,這樣可以方便用戶全屏更清晰的觀察街景內容。
- 旋轉事件
上面我們說過對MapView添加Marker的方法,這裏就派上用場了。隨着我們對PanoView的不斷拖拽旋轉,通過其getPanoramaHeading() 可以得到當前視角的偏航角。
在UI線程中,我們可以通過不斷移除和添加Marker,並設置不同的marker的偏轉角度,從而實現一種在左下方小地圖上呈現我們當前視角的效果。
好了,這樣就簡單模仿了一下百度地圖街景的部分實現功能,由於UI資源所限制,部分效果並非完全一致,這裏只是學習下而已。
代碼已上傳至github,點這裏即可查看。