使用過(guò)百度地圖的同學(xué)知道,它有個(gè)街景功能,可以看到許多地方的實(shí)景。這里就其街景內(nèi)容的實(shí)現(xiàn),進(jìn)行下學(xué)習(xí)。
在百度地圖SDK的官網(wǎng)上可以看到,百度對(duì)開發(fā)者提供了很多相關(guān)的內(nèi)容,方便我們進(jìn)行學(xué)習(xí)。關(guān)于SDK的使用方法,包括jar包導(dǎo)入,*.so 動(dòng)態(tài)庫(kù)的添加位置及AndroidManifest文件的配置不做為我們這里討論的內(nèi)容,官方文檔已經(jīng)介紹的很詳細(xì),不做無(wú)聊的搬運(yùn)工。
效果圖##
這里我們首先預(yù)覽下,今天最終要實(shí)現(xiàn)的效果圖
如圖所示,我們這里的實(shí)現(xiàn),就是兩個(gè)頁(yè)面的內(nèi)容,一個(gè)是基礎(chǔ)的地圖MapView,一個(gè)是街景地圖PanoView。接下來(lái),就這兩個(gè)頁(yè)面(Activity)分別展開來(lái)說(shuō)。(由于GIF圖片大小限制,效果不是很理想,文章結(jié)尾有源碼地址,可以自己跑一下看一下效果先)
地圖MapView實(shí)現(xiàn)##
地圖MapView的簡(jiǎn)單顯示###
布局文件####
<?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####
一般情況下,我們的應(yīng)用程序都會(huì)有一個(gè)繼承自Application的類,用于實(shí)現(xiàn)一些初始化的方法,這里可以在Application里執(zhí)行一些百度地圖初始化的工作,這也是官方提倡的方式。
public void onCreate() {
super.onCreate();
mInstance = this;
SDKInitializer.initialize(getApplicationContext());
}
Activity####
在Activity的OnCreate方法中實(shí)現(xiàn)
setContentView(R.layout.activity_mapview);
mMapView = (MapView) findViewById(R.id.bmapView);
上面這樣一段簡(jiǎn)單的代碼,就可以在Activity中顯示出一個(gè)MapView,也就是我們最熟悉的地圖頁(yè)面,是不是很簡(jiǎn)單,就像我們顯示一個(gè)TextView一樣。
這里說(shuō)明寫一下,按照官方的API指導(dǎo)文檔,使用MapView等百度地圖SDK所提供的各種實(shí)現(xiàn),是需要去申請(qǐng)相關(guān)的key的,申請(qǐng)的方法在官網(wǎng)有著詳細(xì)的介紹,這里就不再粘貼復(fù)制了;很多同學(xué)在使用MapView的時(shí)候發(fā)現(xiàn),程序運(yùn)行后地圖沒有顯示,顯示的都是一些方格子,這往往是由于key沒有申請(qǐng),或申請(qǐng)的方式不當(dāng)造成的
MapView顯示到當(dāng)前位置###
每次打開百度地圖,都會(huì)自動(dòng)定位到我們當(dāng)前所在的位置,或者是我們搜索某個(gè)特定的地方作為新的位置,整個(gè)地圖所呈現(xiàn)的區(qū)域都是新位置周邊的環(huán)境。這里,關(guān)于地圖的定位和搜索的相關(guān)實(shí)現(xiàn)內(nèi)容,就不展開來(lái)說(shuō),不當(dāng)做此次的重點(diǎn)。
假設(shè)我們已通過(guò)定位(或者是搜索),定位了到了一個(gè)位置
**
* 假設(shè)我們當(dāng)前的位置在此
*/
private final double latitude = 39.963175;
private final double longitude = 116.400244;
這個(gè)位置按照新聞里常聽到的說(shuō)法就是,東經(jīng)116.40度,北緯39.96度,位于北京市東城區(qū)舊鼓樓大街丙1號(hào)。
接下來(lái),我們要做的就是將MapView的視圖更新到我們“定位”的位置,這個(gè)位置周邊的地圖才是我們關(guān)心的。
mBaiduMap = mMapView.getMap();
//定義Maker坐標(biāo)點(diǎn)
point = new LatLng(latitude, longitude);
//定義地圖狀態(tài)
final MapStatus mMapStatus = new MapStatus.Builder()
.target(point)
.zoom(18)
.build();
//定義MapStatusUpdate對(duì)象,以便描述地圖狀態(tài)將要發(fā)生的變化
MapStatusUpdate mMapStatusUpdate =
MapStatusUpdateFactory.newMapStatus(mMapStatus);
//改變地圖狀態(tài)
mBaiduMap.setMapStatus(mMapStatusUpdate);
這里的mBaiduMap 是一個(gè)BaiduMap的實(shí)例,通過(guò)MapView的getMap方法即可獲得。我們對(duì)地圖的各種操作,設(shè)置屬性都是基于這個(gè)實(shí)例進(jìn)行。
通過(guò)上面的代碼,我們就可以將MapView的視圖更新到我們所想要的位置了。
添加View到MapView###
添加Marker####
按照百度地圖API的說(shuō)法,我們添加到地圖上的小圖標(biāo)統(tǒng)一稱為Marker。
//構(gòu)建Marker圖標(biāo)
bitmap = BitmapDescriptorFactory
.fromResource(R.drawable.icon_markc);
//構(gòu)建MarkerOption,用于在地圖上添加Marker
option = new MarkerOptions()
.position(point)
.icon(bitmap);
//在地圖上添加Marker,并顯示
mBaiduMap.addOverlay(option);
通過(guò)上面的實(shí)現(xiàn),我們就可以將一個(gè)小圖標(biāo)添加到地圖層,作為標(biāo)記。我們?nèi)粘J褂玫貓D時(shí),所搜周邊后呈現(xiàn)的一系列小圓點(diǎn)就是如此(如下圖)
ShowInfoWindow使用####
最后一步,實(shí)現(xiàn)顯示街景縮略圖的那個(gè)小彈框。
這里首先自定義一下我們要添加到地圖層的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這個(gè)ImageView用于顯示我們要展示的街景縮略圖。pano_overlay是整個(gè)彈框的布局,很簡(jiǎn)單,這里就不貼代碼了。
同時(shí),我們?yōu)檫@個(gè)自定義View設(shè)置點(diǎn)擊事件,方便我們跳轉(zhuǎn)到PanoView街景地圖頁(yè)面,并且將當(dāng)前位置傳遞過(guò)去。
由于祖國(guó)地大物博,所以街景的覆蓋并非百分之百,所以說(shuō),不是每個(gè)地方都有街景可以顯示,有些鳥不拉屎的地方是看不到的。那我們?cè)趺粗朗裁吹胤接薪志澳??API為我們提供了很好的檢測(cè)方法
new Thread(new Runnable() {
@Override
public void run() {
PanoramaRequest request =
PanoramaRequest.getInstance(mContext);
BaiduPanoData locationPanoData =
request.getPanoramaInfoByLatLon(longitude, latitude);
//開發(fā)者可以判斷是否有外景(街景)
if (locationPanoData.hasStreetPano()) {
String url = baseUrl + locationPanoData.getPid();
Message message = new Message();
message.what = 0x01;
message.obj = url;
handler.sendMessage(message);
}
}
}).start();
這樣,我們就可以根據(jù)當(dāng)前位置,先檢測(cè)一下是否有街景可以顯示。這里,如果當(dāng)前位置有街景,我們就通過(guò)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的說(shuō)明及其構(gòu)造函數(shù)
public class InfoWindow extends java.lang.Object
在地圖中顯示一個(gè)信息窗口,可以設(shè)置一個(gè)View作為該窗口的內(nèi)容,也可以設(shè)置一個(gè) BitmapDescriptor 作為該窗口的內(nèi)容。
public InfoWindow(View view, LatLng position, int yOffset)
/**
通過(guò)傳入的 view 構(gòu)造一個(gè) InfoWindow, 此時(shí)只是利用該view
生成一個(gè)Bitmap繪制在地圖中,監(jiān)聽事件由開發(fā)者實(shí)現(xiàn)。
Parameters:
view - InfoWindow 展示的 view
position - InfoWindow 顯示的地理位置
yOffset - InfoWindow Y 軸偏移量
*/
在Handler的handleMessage方法中,我們通過(guò)返回的url加載圖片,并將自定義的彈框View顯示到之前一步添加的marker偏上一點(diǎn)的地方(這就是InfoWindow的構(gòu)造函數(shù)中-57的意義)
關(guān)于這個(gè)加載圖片的URL,可以參考這里靜態(tài)圖API。
這樣,就實(shí)現(xiàn)了MapView頁(yè)面所有的內(nèi)容。通過(guò)點(diǎn)擊InfoWindow,就可以跳轉(zhuǎn)到PanoView所在的界面去查看街景地圖。
接下來(lái),我們將介紹PanoView街景地圖的實(shí)現(xiàn)。
街景地圖PanoViewActivity實(shí)現(xiàn)##
街景地圖PanoView基礎(chǔ)###
街景地圖PanoView的顯示和基礎(chǔ)地圖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類中做一些初始化工作;對(duì)我們所使用key的有效性進(jìn)行檢測(cè)。
public void initEngineManager(Context context) {
if (mBMapManager == null) {
mBMapManager = new BMapManager(context);
}
if (!mBMapManager.init(new MyGeneralListener())) {
Toast.makeText(PanoDemoApplication.getInstance()
.getApplicationContext(), "BMapManager 初始化錯(cuò)誤!",
Toast.LENGTH_LONG).show();
}
}
// 常用事件監(jiān)聽,用來(lái)處理通常的網(wǎng)絡(luò)錯(cuò)誤,授權(quán)驗(yàn)證錯(cuò)誤等
static class MyGeneralListener implements MKGeneralListener {
@Override
public void onGetPermissionState(int iError) {
// 非零值表示key驗(yàn)證未通過(guò)
if (iError != 0) {
// 授權(quán)Key錯(cuò)誤:
Toast.makeText(PanoDemoApplication.getInstance()
.getApplicationContext(),
"請(qǐng)?jiān)贏ndoridManifest.xml中輸入正確的授權(quán)Key,并檢查您的網(wǎng)絡(luò)連接是否正常!error: " + iError, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(PanoDemoApplication.getInstance()
.getApplicationContext(), "key認(rèn)證成功", Toast.LENGTH_LONG)
.show();
}
}
}
同時(shí)在Activity里也需要做一些初始化的工作,最后就是通過(guò)PanoView的setPanorama()方法實(shí)現(xiàn)街景的顯示。
關(guān)于這里用到的setPanorama(),根據(jù)API我們可以看到
public void setPanorama(java.lang.String pid)
//根據(jù)全景pid值切換全景場(chǎng)景
public void setPanorama(int x,int y)
//根據(jù)百度墨卡托投影坐標(biāo)切換全景場(chǎng)景
public void setPanorama(double longitude,double latitude)
//根據(jù)百度經(jīng)緯度坐標(biāo)切換全景場(chǎng)景
public void setPanoramaByUid(java.lang.String uid,
int panoType)
//根據(jù)uid值切換全景場(chǎng)景
也就是說(shuō),不僅通過(guò)經(jīng)緯度,而且可以通過(guò)別的方式實(shí)現(xiàn)街景地圖的功能,甚至室內(nèi)景的實(shí)現(xiàn)。這里我們就使用了大家最熟悉的經(jīng)緯度,對(duì)于別的實(shí)現(xiàn)方式有興趣的同學(xué),可以自己去探索一下。
將地圖MapView展示在街景PanoView上面###
如圖所示,將一個(gè)MapView顯示在PanoView之上;很自然的我們會(huì)寫出下面的布局方式:
<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>
這樣,我們?cè)谡麄€(gè)PanoView的左下角定義一個(gè)60x60大小的view用于顯示一個(gè)MapView。
實(shí)現(xiàn)對(duì)街景視圖操作的監(jiān)聽###
街景SDK為我們提供了PanoramaViewListener這個(gè)接口,可以實(shí)現(xiàn)對(duì)從街景視圖開始繪制到完成繪制,對(duì)街景地圖的操作(如點(diǎn)擊,旋轉(zhuǎn))的監(jiān)聽。
這里我們重點(diǎn)看一下onMessage(String msgName, int msgType)這個(gè)回調(diào)方法。
public void onMessage(String msgName, int msgType) {
Log.e(LTAG, "msgName--->" + msgName + ",
msgType--->" + msgType);
switch (msgType) {
case 8213:
//旋轉(zhuǎn)
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:
//點(diǎn)擊
Log.e(PanoViewActivity.class.getSimpleName(),
"clicked");
Message msg = new Message();
msg.what = ACTION_CLICK;
handler.sendMessage(msg);
break;
default:
break;
}
}
這里不得不吐槽一下,官方所提供的API文檔,對(duì)這個(gè)onMessage回調(diào)方法中的參數(shù)居然沒有任何有價(jià)值的解釋。這里的8213及12302完全是通過(guò)打印日志自己總結(jié)出的規(guī)律。
這樣,我們對(duì)于不同的操作,就可以通過(guò)Handler實(shí)現(xiàn)不同的UI效果。我們看一下handler的實(shí)現(xiàn):
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();
//構(gòu)建MarkerOption,用于在地圖上添加Marker
option = new MarkerOptions()
.position(point)
.rotate(360-heading)
.icon(bitmap);
//在地圖上添加Marker,并顯示
mBaiduMap.addOverlay(option);
break;
default:
break;
}
}
}
這里的處理就分兩種情況:
- 點(diǎn)擊事件
我們仿照百度地圖的樣式,實(shí)現(xiàn)標(biāo)題欄及MapView的隱藏,并添加動(dòng)畫,這樣可以方便用戶全屏更清晰的觀察街景內(nèi)容。
- 旋轉(zhuǎn)事件
上面我們說(shuō)過(guò)對(duì)MapView添加Marker的方法,這里就派上用場(chǎng)了。隨著我們對(duì)PanoView的不斷拖拽旋轉(zhuǎn),通過(guò)其getPanoramaHeading() 可以得到當(dāng)前視角的偏航角。
在UI線程中,我們可以通過(guò)不斷移除和添加Marker,并設(shè)置不同的marker的偏轉(zhuǎn)角度,從而實(shí)現(xiàn)一種在左下方小地圖上呈現(xiàn)我們當(dāng)前視角的效果。
好了,這樣就簡(jiǎn)單模仿了一下百度地圖街景的部分實(shí)現(xiàn)功能,由于UI資源所限制,部分效果并非完全一致,這里只是學(xué)習(xí)下而已。
代碼已上傳至github,點(diǎn)這里即可查看。