Android开发基于百度地图的乘车助手

写在前面:

出去玩免不了挤公交、等地铁,不知道乘车方案当然不行,用官方APP吧,缺点一大堆,手机浏览器在线查的话既慢又麻烦...为了解决这些问题,我们来做一个简版的出行助手,嘛嘛再也不用担心我会迷路了_\^o^/_

(一)功能需求分析

[基础功能]

1.能够根据起点站和终点站查询乘车方案,并显示多种乘车方案

2.能够根据公交路线号查询沿途站点(防止坐过站...)

[扩展功能]

3.GPS定位获取起点站(距离当前位置最近的站点名)[后来放弃了,费电,费流量...]

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.貌似显示地图的方法会自动判断(优先使用离线地图,如果有的话)

(八)运行界面截图

Android开发基于百度地图的乘车助手

Android开发基于百度地图的乘车助手

Android开发基于百度地图的乘车助手

Android开发基于百度地图的乘车助手

Android开发基于百度地图的乘车助手

Android开发基于百度地图的乘车助手

上一篇:有return语句情况下,try-catch-finally的执行顺序


下一篇:iOS 使用UIView的一种有效方法