寫在前面:java
出去玩免不了擠公交、等地鐵,不知道乘車方案固然不行,用官方APP吧,缺點一大堆,手機瀏覽器在線查的話既慢又麻煩...爲了解決這些問題,咱們來作一個簡版的出行助手,嘛嘛不再用擔憂我會迷路了_\^o^/_node
(一)功能需求分析android
[基礎功能]api
1.可以根據起點站和終點站查詢乘車方案,並顯示多種乘車方案瀏覽器
2.可以根據公交路線號查詢沿途站點(防止坐過站...)網絡
[擴展功能]app
3.GPS定位獲取起點站(距離當前位置最近的站點名)[後來放棄了,費電,費流量...]ide
4.顯示地圖[後來也放棄了,地圖對用戶來講好像沒什麼太大用處(固然喜歡走路的另當別論),至少對本人來講地圖沒什麼用]函數
(二)可實現性分析佈局
1.百度地圖開放平臺提供的API能夠實現乘車方案查詢
2.3.4.同上,結論:徹底能夠實現須要的全部功能
(三)開發前提
1.須要BaiDuMap的開發者帳號
2.須要key(如今新版的地圖key與App惟一綁定)
3.須要官方提供的jar包
搜索一下「百度地圖開發」,上面的三件事情分分鐘搞定
[說到這裏不得不讚一下這極低的門檻了,騰訊、新浪微博...的開發者帳號就很難認證,有的甚至須要上傳身份證複印件...]
(四)研究API文檔以及Demo
API文檔說實話作得不怎麼樣,函數詳解都只有一句話,建議直接看Demo,附有大量註釋,簡單易懂
(五)開始編碼(下面給出的源碼都親測可用,並附有最詳細的註釋)
[SearchPlan.java---MainActivity]
package com.ayqy.app_gowithme; import java.util.ArrayList; import android.app.Activity; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.text.InputType; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.baidu.mapapi.BMapManager; import com.baidu.mapapi.search.MKAddrInfo; import com.baidu.mapapi.search.MKBusLineResult; import com.baidu.mapapi.search.MKDrivingRouteResult; import com.baidu.mapapi.search.MKLine; import com.baidu.mapapi.search.MKPlanNode; import com.baidu.mapapi.search.MKPoiInfo; import com.baidu.mapapi.search.MKPoiResult; import com.baidu.mapapi.search.MKRoute; import com.baidu.mapapi.search.MKSearch; import com.baidu.mapapi.search.MKSearchListener; import com.baidu.mapapi.search.MKShareUrlResult; import com.baidu.mapapi.search.MKSuggestionResult; import com.baidu.mapapi.search.MKTransitRoutePlan; import com.baidu.mapapi.search.MKTransitRouteResult; import com.baidu.mapapi.search.MKWalkingRouteResult; public class SearchPlan extends Activity{ BMapManager mBMapMan = null; ListView list; private MKSearch mSearch = null; // 搜索模塊,也可去掉地圖模塊獨立使用 private String city;//城市 private String start;//起點 private String end;//終點 EditText txtCity; EditText txtStart; EditText txtEnd; Button btnSearch; ArrayList<String> plans;//方案列表 ArrayList<String> details;//詳細方案 @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); //獲取SDK使用權限 mBMapMan=new BMapManager(getApplication()); mBMapMan.init("MzKbxcvX7gBEqpeW2kdmOqhx", null); //注意:上面方法的第一個參數必需要填本身申請的key //由於App的PackageName--Shell值--key都惟一對應 //用別人的key確定會出現受權失敗的錯誤,結果就是什麼API都別想用 this.setContentView(R.layout.query); //關聯控件 list = (ListView) findViewById(R.id.List); txtCity = (EditText) findViewById(R.id.txtCity); txtStart = (EditText) findViewById(R.id.txtStart); txtEnd = (EditText) findViewById(R.id.txtEnd); btnSearch = (Button) findViewById(R.id.btnSearch); //設置按鈕半透明 btnSearch.getBackground().setAlpha(204); // txtStart.setText("小居安"); // btnSearch.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //搜索乘車方案 //判空 start = txtStart.getText().toString().trim(); end = txtEnd.getText().toString().trim(); city = txtCity.getText().toString().trim(); if("".equals(start) || "".equals(end) || "".equals(city)) { Toast.makeText(SearchPlan.this, "...到底想去哪裏", Toast.LENGTH_SHORT).show(); return; } else { //搜索方案 searchPlans(); } } }); // 初始化搜索模塊,註冊事件監聽 mSearch = new MKSearch(); mSearch.init(mBMapMan, new MKSearchListener() { @Override public void onGetPoiDetailSearchResult(int type, int error) { } public void onGetDrivingRouteResult(MKDrivingRouteResult res, int error) { } public void onGetTransitRouteResult(MKTransitRouteResult res, int error) { if (error != 0 || res == null) { showError(error); return; } // 獲得解決方案 int maxPlanNum = res.getNumPlan();//方案數 //顯示方案列表 plans = new ArrayList<String>(); details = new ArrayList<String>(); for(int i = 0;i < maxPlanNum;i++) { //獲得詳細方案 MKTransitRoutePlan routePlan = res.getPlan(i); //獲取時耗 int time = routePlan.getTime() / 60; int hour = time / 60; int min = time % 60; // 公交線路 MKLine mkLine = routePlan.getLine(0); //記錄方案詳細信息 StringBuilder sb = new StringBuilder(); sb.append("[預計耗時:"); if(hour > 0)sb.append(hour + " 小時 "); sb.append(min); sb.append("分鐘]\n\n乘坐:"); sb.append(mkLine.getTitle()); MKPoiInfo mkOnPoiInfo = mkLine.getGetOnStop(); MKPoiInfo mkOffPoiInfo = mkLine.getGetOffStop(); sb.append("\n從"); sb.append(mkOnPoiInfo.name); sb.append("上車,在"); sb.append(mkOffPoiInfo.name); sb.append("下車\n[途經 "); sb.append(mkLine.getNumViaStops()); sb.append(" 站]"); if (routePlan.getNumLines() > 0) { // 循環當前方案公交路線 for (int j = 1; j < routePlan.getNumLines(); j++) { // 公交線路 mkLine = routePlan.getLine(j); sb.append("\n換乘:"); sb.append(mkLine.getTitle()); mkOnPoiInfo = mkLine.getGetOnStop(); mkOffPoiInfo = mkLine.getGetOffStop(); sb.append("\n從"); sb.append(mkOnPoiInfo.name); sb.append("上車,在"); sb.append(mkOffPoiInfo.name); sb.append("下車\n[途經 "); sb.append(mkLine.getNumViaStops()); sb.append(" 站]"); } } //填充詳細方案列表 details.add(sb.toString()); //填充方案列表 plans.add("方案 " + (i + 1)); } //顯示方案列表 showPlans(); } public void onGetWalkingRouteResult(MKWalkingRouteResult res, int error) { } public void onGetAddrResult(MKAddrInfo res, int error) { } //線路查詢相關 public void onGetPoiResult(MKPoiResult res, int type, int error) { // 錯誤號可參考MKEvent中的定義 if (error != 0 || res == null) { showError(error); return; } // 找到公交路線poi node MKPoiInfo curPoi = null; int totalPoiNum = res.getNumPois(); for (int idx = 0; idx < totalPoiNum; idx++) { curPoi = res.getPoi(idx); // ePoiType-->poi類型,0:普通點,1:公交站,2:公交線路,3:地鐵站,4:地鐵線路 if (2 == curPoi.ePoiType) { break; } } mSearch.busLineSearch(curPoi.name, curPoi.uid); } //線路查詢相關 public void onGetBusDetailResult(MKBusLineResult result, int error) { if (error != 0 || result == null) { showError(error); return; } //獲取詳細路線 MKRoute route = result.getBusRoute(); int num = route.getNumSteps();//關鍵點數量 //循環獲取關鍵點描述 String[] arrInfo = new String[num + 1]; for(int i = 0;i < num;i++) arrInfo[i + 1] = route.getStep(i).getContent(); //獲取運營時間信息 String busName = result.getBusName(); String startTime = result.getStartTime(); String endTime = result.getEndTime(); arrInfo[0] = busName + "\n首班:" + startTime + "\n末班:" + endTime; //將數據裝入Bundle Bundle data = new Bundle(); data.putStringArray("routeInfo", arrInfo); //新建Intent Intent intent = new Intent(SearchPlan.this,ShowRoute.class); intent.putExtras(data); //啓動對應Activity startActivity(intent); } @Override public void onGetSuggestionResult(MKSuggestionResult res, int arg1) { // TODO Auto-generated method stub } @Override public void onGetShareUrlResult(MKShareUrlResult arg0, int arg1, int arg2) { // TODO Auto-generated method stub } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(android.view.MenuItem item){ if(item.getItemId() == R.id.searchRoute) {//顯示線路查詢對話框 //建立Builder對象 Builder builder = new Builder(SearchPlan.this); builder.setTitle("查詢線路詳情"); builder.setIcon(R.drawable.search); //設置對話框內容 LinearLayout view = new LinearLayout(SearchPlan.this); view.setOrientation(LinearLayout.HORIZONTAL); TextView lbl1 = new TextView(SearchPlan.this); lbl1.setText("查詢"); lbl1.setTextSize(18); final EditText txtCity = new EditText(SearchPlan.this); txtCity.setHint("城市名"); txtCity.setText("西安"); TextView lbl2 = new TextView(SearchPlan.this); lbl2.setText("公交"); lbl2.setTextSize(18); final EditText txtBus = new EditText(SearchPlan.this); txtBus.setInputType(InputType.TYPE_CLASS_NUMBER);//限輸入數字 txtBus.setSingleLine();//單行 txtBus.setHint("線路名"); TextView lbl3 = new TextView(SearchPlan.this); lbl3.setText("路"); lbl3.setTextSize(18); view.addView(lbl1); view.addView(txtCity); view.addView(lbl2); view.addView(txtBus); view.addView(lbl3); builder.setView(view); //添加按鈕 builder.setPositiveButton("查詢", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //查詢路線信息 //判空 String city = txtCity.getText().toString().trim(); String bus = txtBus.getText().toString().trim(); if("".equals(city) || "".equals(bus)) { Toast.makeText(SearchPlan.this, "...到底想查詢什麼", Toast.LENGTH_SHORT).show(); return; } //顯示提示信息 showTip(); //POI搜索 mSearch.poiSearchInCity(city, bus); } }); builder.setNegativeButton("取消", null); //顯示對話框 builder.create().show(); } if(item.getItemId() == R.id.exit) finish();//退出程序 return true; }; //自定義方法 private void searchPlans() {//搜索乘車方案 //對起點終點的name進行賦值,也能夠直接對座標賦值,賦值座標則將根據座標進行搜索 MKPlanNode stNode = new MKPlanNode(); stNode.name = start; MKPlanNode enNode = new MKPlanNode(); enNode.name = end; //顯示提示信息 showTip(); //搜索 mSearch.transitSearch(city, stNode, enNode); } private void showPlans() { //爲ListView綁定Adapter ArrayAdapter<String> adapter = new ArrayAdapter<String>(this , android.R.layout.simple_list_item_multiple_choice , plans); list.setAdapter(adapter); list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); //爲ListView添加ItemClick事件監聽 list.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int index, long arg3) { //跳轉到ListActivity,同時傳遞索引參數 //建立Intent Intent intent = new Intent(SearchPlan.this ,ShowDetail.class); //建立Bundle攜帶數據 Bundle bundle = new Bundle(); bundle.putInt("index", index); bundle.putString("detail", details.get(index)); //數據綁定 intent.putExtras(bundle); //啓動Intent對應的Activity startActivity(intent); } }); } private void showError(int error) { String msg = null; switch(error) { case 2:msg="T_T網絡鏈接錯誤...";break; case 3:msg="T_T網絡數據錯誤...";break; case 4:msg="T_T路線搜索起點或終點有歧義...";break; case 100:msg="T_T未找到搜索結果...";break; case 300:msg="T_T受權驗證失敗...";break; default:msg="T_T未知錯誤..."; } Toast.makeText(SearchPlan.this, msg, Toast.LENGTH_LONG).show(); } private void showTip() {//顯示提示信息 Toast.makeText(SearchPlan.this , "(*>.<*)正在拼命搜索..." , Toast.LENGTH_LONG).show(); } //重寫baiduMap中的方法 @Override protected void onDestroy() { mSearch.destory(); if(mBMapMan!=null){ mBMapMan.destroy(); mBMapMan=null; } super.onDestroy(); } @Override protected void onPause(){ if(mBMapMan!=null){ mBMapMan.stop(); } super.onPause(); } @Override protected void onResume(){ if(mBMapMan!=null){ mBMapMan.start(); } super.onResume(); } }
[ShowDetail.java---顯示乘車方案詳情]
package com.ayqy.app_gowithme; import android.app.ListActivity; import android.content.Intent; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; public class ShowDetail extends ListActivity{ ListView root; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); //設置背景 root = getListView(); root.setBackgroundResource(R.drawable.blue_bg); //獲取數據 Intent intent= getIntent(); Bundle bundle = intent.getExtras(); int index = bundle.getInt("index") + 1; String detail = bundle.getString("detail"); //設置Adapter String[] arr = new String[]{"方案 " + index + " 詳情:", detail}; ArrayAdapter<String> adapter = new ArrayAdapter<String>(this , android.R.layout.simple_list_item_1 , arr); this.setListAdapter(adapter); } }
[ShowRoute.java---顯示路線詳情(沿途站點信息)]
package com.ayqy.app_gowithme; import android.app.ListActivity; import android.content.Intent; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; public class ShowRoute extends ListActivity{ ListView root; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); //設置背景 root = getListView(); root.setBackgroundResource(R.drawable.blue_bg); //獲取數據 Intent intent= getIntent(); Bundle bundle = intent.getExtras(); String[] arrInfo = bundle.getStringArray("routeInfo"); //設置Adapter ArrayAdapter<String> adapter = new ArrayAdapter<String>(this , android.R.layout.simple_list_item_1 , arrInfo); this.setListAdapter(adapter); } }
[佈局文件query.xml]
<?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:gravity="bottom" android:background="@drawable/blue_bg" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:text="@string/city" android:textSize="24sp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <EditText android:id="@+id/txtCity" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/cityHint" android:text="@string/XiAn" android:singleLine="true" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:text="@string/start" android:textSize="24sp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <EditText android:id="@+id/txtStart" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/stationHint" android:singleLine="true" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:text="@string/end" android:textSize="24sp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <EditText android:id="@+id/txtEnd" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/stationHint" android:singleLine="true" /> </LinearLayout> <Button android:id="@+id/btnSearch" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" android:text="@string/searchPlan" /> <ListView android:id="@+id/List" android:layout_width="match_parent" android:layout_height="wrap_content" > </ListView> </LinearLayout>
P.S.源碼都在上面,若有疑問請在下方留言
(六)顯示地圖
[最早實現的就是這個(第一次開發地圖有點激動,想看看地圖長什麼樣子...),在需求中本沒打算設計,雖而後來放棄了,但下面的源碼仍然親測無誤]
[ShowMap.java---顯示地圖]
package com.ayqy.app_gowithme; import android.app.Activity; import android.graphics.Bitmap; import android.os.Bundle; import android.view.Menu; import android.widget.TextView; import android.widget.Toast; import com.baidu.mapapi.BMapManager; import com.baidu.mapapi.map.MKMapViewListener; import com.baidu.mapapi.map.MapController; import com.baidu.mapapi.map.MapPoi; import com.baidu.mapapi.map.MapView; import com.baidu.platform.comapi.basestruct.GeoPoint; public class ShowMap extends Activity { BMapManager mBMapMan = null; MapView mMapView = null; MapController mMapController = null; TextView txt; boolean pressAgain = false; long pressTime = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBMapMan=new BMapManager(getApplication()); mBMapMan.init("MzKbxcvX7gBEqpeW2kdmOqhx", null); //注意:請在試用setContentView前初始化BMapManager對象,不然會報錯 setContentView(R.layout.activity_main); mMapView=(MapView)findViewById(R.id.bmapsView); mMapController=mMapView.getController(); //txt = (TextView) findViewById(R.id.txt); // 獲得mMapView的控制權,能夠用它控制和驅動平移和縮放 GeoPoint point =new GeoPoint((int)(34.151884* 1E6),(int)(108.882024* 1E6)); //34.151884,108.882024西北大學16級別 //用給定的經緯度構造一個GeoPoint,單位是微度 (度 * 1E6) mMapController.enableClick(true);//設置地圖響應點擊事件 mMapController.setCenter(point);//設置地圖中心點 mMapController.setZoom(16);//設置地圖zoom級別 mMapView.setBuiltInZoomControls(false);//不顯示內置縮放控件 //建立MKMapViewListener MKMapViewListener listener = new MKMapViewListener() { @Override public void onMapMoveFinish() { // TODO Auto-generated method stub } @Override public void onMapLoadFinish() { } @Override public void onMapAnimationFinish() { // TODO Auto-generated method stub } @Override public void onGetCurrentMap(Bitmap arg0) { // TODO Auto-generated method stub } @Override public void onClickMapPoi(MapPoi arg0) { //點到地圖標註時顯示詳細 if(arg0 != null) { GeoPoint point = arg0.geoPt;//獲取GEO座標 mMapController.setCenter(point);//設置地圖中心點 mMapController.zoomIn();//放大一個級別 } } }; //爲map註冊監聽器 mMapView.regMapViewListener(mBMapMan, listener); } @Override public void onBackPressed() { //按下返回鍵,縮小地圖 float min = 12f;//設置最小縮放級別 if(mMapView.getZoomLevel() > min) mMapController.zoomOut();//縮小一個級別 else { if(pressAgain && (System.currentTimeMillis() - pressTime < 1000)) super.finish();//一秒內再按結束程序 else pressAgain = false; if(!pressAgain) { Toast.makeText(ShowMap.this, "再按一次退出...", Toast.LENGTH_SHORT).show(); pressTime = System.currentTimeMillis();//獲取第一次按下的時間 pressAgain = true; } } }; @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(android.view.MenuItem item){ if(item.getItemId() == R.id.exit) finish();//退出程序 return true; }; //重寫baiduMap中的方法 @Override protected void onDestroy() { mMapView.destroy(); if(mBMapMan!=null){ mBMapMan.destroy(); mBMapMan=null; } super.onDestroy(); } @Override protected void onPause(){ mMapView.onPause(); if(mBMapMan!=null){ mBMapMan.stop(); } super.onPause(); } @Override protected void onResume(){ mMapView.onResume(); if(mBMapMan!=null){ mBMapMan.start(); } super.onResume(); } }
[佈局文件activity_main.xml]
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:scrollbars="none" > <com.baidu.mapapi.map.MapView android:id="@+id/bmapsView" android:layout_width="match_parent" android:layout_height="match_parent" android:clickable="true" /> </LinearLayout>
(七)離線地圖
離線地圖好處多多,但如果要開發須要推廣給衆多用戶的App的話,建議要麼作下載離線地圖包功能(Demo中有例程,很容易),要麼考慮在App第一次運行的時候把APK資源文件中的離線地圖複製到用戶SD卡中(理論上能夠實現),固然這樣的話地圖適用範圍會受到限制,開發有明確地域限制的App能夠選用(例如:西安出行助手)。
離線地圖是這樣用的:
1.把從官網下載的文件夾整個複製到手機SDCARD指定路徑(具體放哪裏請看壓縮包中的ReadMe.txt介紹)
2.在程序中須要對離線地圖包進行scan初始化(其本質是對地圖包的解析,把一個大文件變成了幾個小文件,因此,不進行解析的話地圖包是不能用的)
3.貌似顯示地圖的方法會自動判斷(優先使用離線地圖,若是有的話)
(八)運行界面截圖