9.1 工作目标 9.1.1、终极目标
实现地图图层切换、设置地图覆盖物、兴趣点的多条目检索、驾车路线检索、公交路线检索、步行路线检索和定位功能等。
图9.1.1.1 卫星图层
图9.1.1.2公交换乘检索
图9.1.1.3兴趣点的检索
图9.1.1.4设置地图覆盖物
图9.1.1.5定位功能
9.1.2、促成目标
(1) 完成项目分析。 (2) 完成界面设计。 (3) 完成代码编写。
(4)完成系统运行与效果测试。
9.2 工作任务
工作任务一:项目分析 工作任务二:界面设计 工作任务三:代码编写
工作任务四:系统运行与效果测试
9.3 项目分析
本项目基于百度地图api,主要为百度地图的入门与加强。
入门:将地图展示在手机上并且可以随意放大缩小、平移、旋转和切换图层。
加强:能够在地图上添加文字或图形覆盖物并为覆盖物添加点击事件,可以进行兴趣点、驾车、步行和公交线路的检索,完成定位功能。
项目流程:
主页面为listview通过点击listview中的item进入对应的界面,完成对地图的基本操作。
项目中的和心类:
1、BMapManager地图管理类 2、MapView地图视图类
MapView 的MKMapViewListener 3、MapController地图控制类
项目所用sdk:
baidumapapi_v2_1_2.jar locSDK_3.1.jar
项目中的key:1A4A4ABEFBEECD8C17DEE880C4EA69B9607020B5
9.4 界面设计 9.4.1 知识准备
对于百度地图的展示和操作需要设计到用户隐私等必须添加一下的用户权限
并且在定位时需要添加service,下文会详细解释。 9.4.2 项目实施 地图视图界面设计:(代码清单:CH_1_界面设计) 要想将百度地图在手机界面上显示出来,需要用到他提供的控件MapView,即使用规定的名称com.baidu.mapapi.map.MapView. 效果图: 图9.4.2.1MapView 子视图界面设计:(代码清单:CH_1_界面设计) 子视图是用来在覆盖物上显示其详细信息的,在作为地图的子视图是需要控制视图和其中控件的大小。 android:gravity=\"center_horizontal\"> android:ellipsize=\"end\" android:text=\"标题\" android:textColor=\"@android:color/white\" android:textSize=\"20sp\"/> 效果图: 图9.4.2.2 MapView 主界面设计:(代码清单:CH_1_界面设计) 主界面为listview,点击不同的item跳转到对应的地图操作界面。 android:layout_height=\"wrap_content\"> 效果图: 图9.4.2.3 listview 9.5 代码编写 9.5.1 知识准备 SDK下载:http://lbsyun.baidu.com/sdk/download Key申请:http://lbsyun.baidu.com/apiconsole/key(需要登录账号) 申请key的流程: 1、 登录百度账号 2、 进入我的应用并创建应用 3、 选择应用类型为Android SDK并配置安全码(eclipse数字签名和包名) 4、 复制得到的KEY并加入清单文件 流程图: 图9.5.1.1 登录百度账号 图9.5.1.2 查看应用界面 图9.5.1.3创建应用 图9.5.1.4 查看Android数字签名 图9.5.1.5得到KEY 9.5.2 项目实施 地图基本数据逻辑代码:(代码清单:CH_2_逻辑代码实现) 首先是前文介绍到的百度地图三大核心类:BMapManager、MapView和MapController。 依次分别为管理、视图和控制。 protected BMapManager manager;//地图引擎管理 protected MapView mapView;//地图的视图控件 protected MapController controller;//控制地图:平移缩放旋转 在获取地图数据前必须要对申请的key进行验证,否则就会导致程序崩溃,所以此项验证必须放在获取视图之前。此时就需要用到BMapManager类了。在init()方法中需要设置监听事件来判断验证是否通过和当前网路状况。其中的PromptManager类为工具类,后面会介绍到。 privatevoid checkKey() { manager = new BMapManager(getApplicationContext()); //验证key manager.init(ConstantValue.KEY, new MKGeneralListener() { @Override publicvoid onGetPermissionState(int iError) { //授权验证 if(iError == MKEvent.ERROR_PERMISSION_DENIED){ } PromptManager.showToastTest(BaseActivity.this, \" 授权验证失败\"); } } @Override publicvoid onGetNetworkState(int iError) { } //没有网络 if(iError == MKEvent.ERROR_NETWORK_CONNECT){ } PromptManager.showNoNetWork(BaseActivity.this); }); 设置地图数据。主要为在界面上显示按钮、设置地图的默认视距和地图默认中心点等等。需要注意的是MapController对象需要在MapView对象存在的情况下才可以得到,因为必须指明控制的是哪一个地图视图。 privatevoid initView() { // TODO Auto-generated method stub mapView = (MapView) findViewById(R.id.mv_information); //显示内置放大和缩小的按钮 mapView.setBuiltInZoomControls(true); //控制地图的缩放 controller = mapView.getController();//必须现有mapview再有controller 为了在打开地图时可以及时的将数据展现在视图上而不至于出现混乱,需要将地图视图的生命周期与activity进行绑定。 @Override 该类为MKSearchListener接口的实现,这样就可以直接继承该类并复写的方法使用接口的方法而不至于每次在实现接口时同时实现所有的方法,从而提高效率。 protectedclass MySearchListenerAdapter implements MKSearchListener{ @Override publicvoid onGetAddrResult(MKAddrInfo result, int iError) { // TODO Auto-generated method stub protectedvoid onResume() { } @Override protectedvoid onPause() { } @Override protectedvoid onDestroy() { } mapView.destroy(); super.onDestroy(); mapView.onPause(); super.onPause(); mapView.onResume(); super.onResume(); controller.setZoom(12); controller.enableClick(true); controller.setCenter(point);//设置地图默认中心点 } { } @Override publicvoid onGetBusDetailResult(MKBusLineResult result, int } @Override publicvoid onGetDrivingRouteResult(MKDrivingRouteResult } @Override publicvoid onGetPoiDetailSearchResult(int type, int iError) } @Override publicvoid onGetPoiResult(MKPoiResult result, int type, int } @Override publicvoid onGetSuggestionResult(MKSuggestionResult result, } @Override publicvoid onGetTransitRouteResult(MKTransitRouteResult int iError) { // TODO Auto-generated method stub // TODO Auto-generated method stub // TODO Auto-generated method stub int iError) { // TODO Auto-generated method stub iError) { result, // TODO Auto-generated method stub iError) { int iError) { result, // TODO Auto-generated method stub } @Override publicvoid onGetWalkingRouteResult(MKWalkingRouteResult } int iError) { result, // TODO Auto-generated method stub 效果图: 图9.5.2.1 基本地图 地图图层逻辑代码:(代码清单:CH_2_逻辑代码实现) 设置交通图的显示 mapView.setTraffic(false); 设置卫星图的显示 mapView.setSatellite(false); 设置键盘点击事件监听来切换不同的图层 @Override publicboolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub switch (keyCode) { case KeyEvent.KEYCODE_1: //底图 mapView.setTraffic(false); mapView.setSatellite(false); break; } } //交通 mapView.setTraffic(true); mapView.setSatellite(false); break; //卫星 mapView.setSatellite(true); mapView.setTraffic(false); break; case KeyEvent.KEYCODE_2: case KeyEvent.KEYCODE_3: returnsuper.onKeyDown(keyCode, event); 效果图: 图9.5.2.2 实时交通图 图9.5.2.3 卫星图 几何图形覆盖物逻辑代码:(代码清单:CH_2_逻辑代码实现) 该类继承自基本地图视图类,这样就可以省去获取地图数据的代码,提高代码编写效率。 绘制覆盖物,一般对地图视图的操作分为四步: 1、 定义操作 2、 设置数据 3、 将操作对象加入视图中 4、 刷新视图 只有第一步和第二步有所差异,其中第二步最为繁琐。 privatevoid draw() { 为覆盖物对象设置数据。Symbol为样式类定义操作对象的样式。 privatevoid setData(GraphicsOverlay overlay) { //定义几何图形:圆心+半径 Geometry geometry = new Geometry(); geometry.setCircle(point, 100); //定义几何图形的样式:颜色+线条等 Symbol symbol = new Symbol(); } //定义覆盖物 //设置覆盖物数据 //获取mapview中存放覆盖物的集合,添加覆盖物 //手动刷新界面 GraphicsOverlay overlay = new GraphicsOverlay(mapView); setData(overlay);//重点:数据设置 mapView.getOverlays().add(overlay); mapView.refresh(); } Symbol.Color color = symbol.new Color(); color.red = 255; color.green = 0; color.blue = 0; color.alpha = 100; symbol.setSurface(color, 1, 0); //几何图形元素数据 Graphic graphic = new Graphic(geometry, symbol); //设置数据 overlay.setData(graphic); 效果图: 图9.5.2.4 几何图形覆盖物 文字覆盖物逻辑代码:(代码清单:CH_2_逻辑代码实现) 基本操作与几何图形覆盖物类似,参见上面解释。 privatevoid draw() { 对文字覆盖物的数据设置 privatevoid setData(TextOverlay overlay) { TextItem item = new TextItem(); item.align = TextItem.ALIGN_CENTER;//对齐方式 item.fontColor = getColor(); } TextOverlay overlay = new TextOverlay(mapView); setData(overlay); mapView.getOverlays().add(overlay); mapView.refresh(); } item.fontSize = 20; item.pt = point;//坐标点 item.text = \"常信院\"; item.typeface = Typeface.DEFAULT_BOLD;//粗体 overlay.addText(item); private Color getColor() { } Symbol symbol = new Symbol(); Symbol.Color color = symbol.new Color(); color.red = 255; color.green = 0; color.blue = 0; color.alpha = 100; return color; 效果图: 图9.5.2.5 文字覆盖物 多条目绘制逻辑代码:(代码清单:CH_2_逻辑代码实现) 添加点击覆盖物时弹出的pop,即子视图。因为未点击时不知道坐标,所以暂时隐藏。 privatevoid initPop() { pop = View.inflate(this, R.layout.pop, null); titleTextView = (TextView) pop.findViewById(R.id.tv_pop_title); pop.setVisibility(View.INVISIBLE); //添加到mapview的容器里 mapView.addView(pop); } 设置pop显示时所展现的内容和显示的坐标点 privatevoid draw() { //装OverlayItem的集合 ItemizedOverlay ItemizedOverlay 为覆盖物设置数据,因为ItemizedOverlay类实际为一个装OverlayItem的集合,所以可以自行添加多个覆盖物。 privatevoid setData(ItemizedOverlay /** * 参1:坐标参2:标题参3:介绍 */ OverlayItem item = new OverlayItem(point, \"常信院\", \"计算机信息职 } }; setData(overlay); mapView.getOverlays().add(overlay); mapView.refresh(); //覆盖物的点击事件 @Override protectedboolean onTap(int index) { } OverlayItem item = this.getItem(index); String title = item.getTitle(); titleTextView.setText(title); /** * 创建自定义布局参数,按地理坐标布局 * 参1:高度参2:宽度参3:坐标点参4:对齐方式 */ MapView.LayoutParams params = new MapView.LayoutParams( MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT, item.getPoint(), MapView.LayoutParams.BOTTOM_CENTER ); //更新pop的位置:将params与经纬度坐标建立关系 mapView.updateViewLayout(pop, params); pop.setVisibility(View.VISIBLE); returnsuper.onTap(index); 业技术学院\"); overlay.addItem(item); item = new OverlayItem(new GeoPoint(latitude+1000,longitude), \" 向北\", \"增加维度\"); overlay.addItem(item); item = new OverlayItem(new GeoPoint(latitude,longitude+1000), \" 向东\", \"增加经度\"); overlay.addItem(item); item = new OverlayItem(new GeoPoint(latitude-1000,longitude-1000), \"向西南\", \"减少经纬度\"); overlay.addItem(item); } 效果图: 图9.5.2.6 多个覆盖物 图9.5.2.7 覆盖物的点击事件 附近兴趣点检索逻辑代码:(代码清单:CH_2_逻辑代码实现) 之后的集合类因为涉及到检索需要使用MKSearch以及他的监听MKSearchListener。 private MKSearch search; 兴趣点检索 privatevoid search() { search = new MKSearch(); listener = new MySearchListenerAdapter(){ @Override publicvoid onGetPoiResult(MKPoiResult result, int type, int // TODO数据显示 if(iError == 0){ if(result != null){ } PoiOverlay overlay = new setData(overlay,result); mapView.getOverlays().add(overlay); mapView.refresh(); private MKSearchListener listener; iError) { PoiOverlay(PoiSearchNearByDemo.this, mapView); }else{ PromptManager.showToastTest(PoiSearchNearByDemo.this, \"未查询到结果\"); 设置覆盖物数据 protectedvoid setData(PoiOverlay overlay, MKPoiResult result) { 得到全部检索到的兴趣点 ArrayList } overlay.setData(datas ); } }; search.init(manager, listener); search.poiSearchNearBy(\"加油站\", point, 5000); } } 使用附近检索的方式来检索加油站 效果图: 图9.5.2.8 附近兴趣点检索 全城兴趣点检索逻辑代码:(代码清单:CH_2_逻辑代码实现) 全城检索的逻辑步骤与附近检索基本相同,唯一的区别为全城检索自带分页。因为按城市检索出来的兴趣点数量太大而视图默认最多显示十条。 因为有分页所以需要清除旧数据 PoiOverlay overlay = newPoiOverlay(PoisearchInCityDemo.this, mapView); setData(overlay,result); mapView.getOverlays().clear();//有分页的情况下点击 下一页后需要先清除上一次的检索记录 mapView.getOverlays().add(overlay); 使用全城兴趣点检索加油站 search.poiSearchInCity(\"常州\", \"加油站\"); 为了能够清楚的看到分页的情况,此处用了子视图展示了当前一共有多少条兴趣点和总页数。 子视图的创建和显示方法上文已经介绍过,这里就不在重复介绍。 protectedvoid setData(PoiOverlay overlay, MKPoiResult result) { ArrayList 目(默认10条) overlay.setData(datas); String info = \"当前页:\"+result.getPageIndex()+ \"/共\"+result.getNumPages()+ \"页 当前页条目数:\"+result.getCurrentNumPois()+ \"/总条目数:\"+result.getNumPois(); mapView.refresh(); PromptManager.showErrorDialog(PoisearchInCityDemo.this, info); } 此方法显示current值对应的页数,需要注意的是页码是从0开始的,所以在对翻页的最大最小值进行限制时需要格外注意。 search.goToPoiPage(current); 效果图: 图9.5.2.9分页显示 驾车或步行线路检索逻辑代码:(代码清单:CH_2_逻辑代码实现) 设置监听事件并在其中进行数据操作。 listener = new MySearchListenerAdapter(){ @Override 此方法为驾车回调函数如果是步行需要使用onGetWalkingRouteResult publicvoidonGetDrivingRouteResult(MKDrivingRouteResult result, int iError) { if(result != null){ } PromptManager.showToastTest(DrivingSearchDemo.this, //路线线条:驾车步行 RouteOverlay overlay = new setData(overlay,result); mapView.getOverlays().clear(); mapView.getOverlays().add(overlay); mapView.refresh(); if(iError == 0){ RouteOverlay(DrivingSearchDemo.this, mapView); }else{ \"为索索到结果\");}}}; 将管理类和监听添加到search中 search.init(manager, listener); 设置起始点和终点,可以是名称或坐标点。 MKPlanNode start = new MKPlanNode(); start.pt = point; MKPlanNode end = new MKPlanNode(); end.name = \"春秋淹城\"; //search.drivingSearch(\"常州\常州\ 设置途径点,可以为多个,这样线路就会在起始点、途经点和终点之间形成 ArrayList MKWpNode node = new MKWpNode(); node.city = \"常州\"; node.name = \"中华恐龙园\"; nodes.add(node); //驾车策略必须在搜索之前设置 search.setDrivingPolicy(MKSearch.ECAR_FEE_FIRST); 使用驾车路线检索 search.drivingSearch(\"常州\", start, \"常州\", end, nodes); 使用步行路线检索 search.walkingSearch(\"常州\", start, \"常州\", end); 效果图: 图9.5.2.10 驾车路线检索 图9.5.2.11 步行路线检索 公交换乘检索逻辑代码:(代码清单:CH_2_逻辑代码实现) listener = new MySearchListenerAdapter(){ 公交换乘监听回调函数 @Override publicvoidonGetTransitRouteResult(MKTransitRouteResult int iError) { if(result != null){ } PromptManager.showToastTest(TransitOverLayDemo.this, //公交换乘需要使用的覆盖物 TransitOverlay overlay = new setData(overlay,result); mapView.getOverlays().add(overlay); mapView.refresh(); result, if(iError == 0){ TransitOverlay(TransitOverLayDemo.this, mapView); }else{ \"为查询到结果\"); } 乘车策略 search.setTransitPolicy(MKSearch.EBUS_WALK_FIRST); 公交换乘检索函数只可以在本城市内检索而步行和驾车可以跨城市检索 }; } search.transitSearch(\"常州\", start, end); 效果图: 图9.5.2.12 公交换乘路线检索 定位功能逻辑代码:(代码清单:CH_2_逻辑代码实现) 定位功能属于高级操作需要locSDK_3.1.jar包的支持,并且在清单文件的application结点下加入service结点 在地图数据显示之前设置定位数据并开启定位 @Override 在视图不可见时关闭定位 @Override //设置数据如:间隔多长时间发送获取位置的请求等 protectedvoid onPause() { } client.stop(); super.onPause(); protectedvoid onResume() { } location(); client.start(); super.onResume(); LocationClientOption option = new LocationClientOption(); option.setOpenGps(true); option.setAddrType(\"all\");//返回的定位结果包含地址信息 option.setCoorType(\"bd0911\");//返回的定位结果是百度经纬度,默认值gcj02 该类用于对定位的监听,开启后在定位关闭前周期性回调 privateclass MyDBLocationListener implements BDLocationListener{ @Override publicvoid onReceivePoi(BDLocation arg0) { } @Override publicvoid onReceiveLocation(BDLocation location) { if(location == null){ } //定位覆盖物 MyLocationOverlay overlay = new MyLocationOverlay(mapView); LocationData data = new LocationData(); data.latitude = location.getLatitude(); data.longitude = location.getLongitude(); overlay.setData(data); mapView.getOverlays().add(overlay); mapView.refresh(); return ; option.setScanSpan(5000);//设置发起定位请求的间隔时间 option.disableCache(true);//禁止启用缓存定位 option.setPoiNumber(5);//最多返回poi的个数 option.setPoiDistance(1000);//poi的查询距离 option.setPoiExtraInfo(true);//是否需要poi的电话和地址等详细信息 client.setLocOption(option); client.registerLocationListener(listener); //模拟器定位 controller.animateTo(new GeoPoint((int)(data.latitude * 1E6), } (int)(data.longitude * 1E6))); } 因为模拟器没有sm卡所以无法使用定位功能,只能显示一个固定的点,代码已在真机上测试过,可以成功定位但不太精确。 效果图: 图9.5.2.13 定位功能 9.6 系统运行与效果测试 系统在模拟器和真机上均可流畅显示,但在真机上会有标注物减少和找不到检索点的情况,所以导致部分线路检索无法正常显示,需在以后的学习中进一步完善代码。 运行效果图已全部在代码编写部分展示,此处就不在重复的贴出来。 因篇幅问题不能全部显示,请点此查看更多更全内容