终于讲到GPS了!现在的LBS如火如荼的发展,Foursquare更是日进千里!有很多圈里人说,创业团队如果想在Android方面创业,最好是开发基于地理位置的应用。可以把电子商务、SNS、Android智能设备完美的融合在一起。今天我们的主角就是GPS。
在Android开发过程中,一直有人在问,怎么在地图上绘制文本或加载图片。今天我们就来实现这个功能,先基础后深入。慢慢的积累才能在GPS这块稳步的了解更多。废话不多说,直接上图,有图有真相嘛。呵呵,截图如下:
(图1) (图2)
图1:是运行程序后出现的界面;
图2:是单价天涯海角后出现的界面;
根据上面截图,我们来完成整个Demo的开发过程。
1、当然,首先我们的申请Google Map服务,至于怎么申请,网上有很多的参考资料或查看开发文档,这里我就不说废话。
2、下载Google API,可能有些童靴在安装Android SDK时,没有把GoogleAPI顺带安装了。由于开发Google Map应用需要基于google aip,所以这里我们就要安装这个。至于怎么安装,我这里提供两种方法:(1)、Window ->Android SDK android AVD Manager->Available Package--->出现Google APIs by Google Inc下载;(2)、直接在网上下载google_apis-8文件,之后将此文件存放在add-ons文件中,之后重启eclipse工具,再按照上面的就出现你想要的了。
3、好了,预前准备都做好之后,该真枪实战了。呵呵,首先新建一个项目,命名为ScenicDisplay。
4、编写布局文件,代码如下:
<?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:padding="0px" android:layout_margin="0px"> <TextView android:id="@+id/textview" android:layout_width="fill_parent" android:layout_height="50px" android:background="#F0FFFF" android:textColor="#0000CD" android:padding="8dp" android:text="" /> <com.google.android.maps.MapView android:id="@+id/map" android:layout_width="fill_parent" android:layout_height="fill_parent" android:apiKey="这里的apiKey写上你自己申请的" android:clickable="true"/> <LinearLayout android:id="@+id/zoom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:gravity="center_horizontal" /> </LinearLayout>
5、在drawable-mdpi文件夹中存放一张图片,也就是上图中显示的五角星,图片可以自己添加设置一张。
6、编写主程序Activity代码,如下:
package com.wyf.wpf; import java.util.ArrayList; import java.util.List; import android.app.AlertDialog; import android.content.DialogInterface; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; import android.widget.Toast; import com.google.android.maps.GeoPoint; import com.google.android.maps.ItemizedOverlay; import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView; import com.google.android.maps.MyLocationOverlay; import com.google.android.maps.Overlay; import com.google.android.maps.OverlayItem; import com.google.android.maps.Projection; public class ScenicDisplayActivity extends MapActivity { // 声明变量 private TextView textView; private MapView mapView; /* * 控制地图移动、伸缩,以某个GPS坐标为中心,控制MapView中的View组件,管理Overlay提供View的基本功能。 */ private MapController mapController; /* * Overlay:此类是一种专门在Target中的选择图上用2D图像进行标记的类,可用于覆盖在地 * 图(MapView)表面的图层,可以把它当成一个画板(Canvas),在重叠的Overlay对象上 * 绘制线条、地标。它有两个子类为ItemizedOverlay类、MyLocationOverlay类 */ // 声明图层 private positionOverlay pOverlay; private MyLocationOverlay myLocationOverlay; // 声明菜单 private static final int MENU_CC = Menu.FIRST; private static final int MENU_SUN = Menu.FIRST + 1; private static final int MENU_TYHJ = Menu.FIRST + 2; // 声明数组,用于存放经纬度 private GeoPoint[] position; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); textView = (TextView) findViewById(R.id.textview); mapView = (MapView) findViewById(R.id.map); // 通过MapView来取得MapController对象 mapController = mapView.getController(); //运用MapController类的setZoom方法控制地图缩放的尺度,数值越大,地图的细节就越详细 mapController.setZoom(17); // 设置地图模式 mapView.setTraffic(true); /* * Map的zoom采用了built-in机制,可以通过setBuiltInZoomControls(boolean) * 来设置是否在地图上显示zoom控件 */ mapView.setBuiltInZoomControls(true); // 获取经纬度值 getPosition(); getView(mapView); } // 设置经纬度 public void getPosition() { position = new GeoPoint[3]; position[0] = new GeoPoint((int) (40.362642 * 1000000), (int) (116.019793 * 1000000)); position[1] = new GeoPoint((int) (45.789361 * 1000000), (int) (126.600048 * 1000000)); position[2] = new GeoPoint((int) (18.292023 * 1000000), (int) (109.347711 * 1000000)); } // 创建菜单选项 @Override public boolean onCreateOptionsMenu(Menu menu) { // TODO Auto-generated method stub menu.add(0, MENU_CC, 0, "长城八达岭"); menu.add(0, MENU_SUN, 1, "太阳岛"); menu.add(0, MENU_TYHJ, 2, "天涯海角"); return super.onCreateOptionsMenu(menu); } // 监听菜单选项事件 @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO Auto-generated method stub switch (item.getItemId()) { case MENU_CC: mapController.animateTo(position[0]); showDialog("世界文化遗产、国家5A级景区、国家重点文物保护单位、" + "国家重点风景名胜区、中国旅游胜地四十佳之首、全国文明旅游风景示范区。"); break; case MENU_SUN: mapController.animateTo(position[1]); textView.setText("是国内最大的城市中心江曼滩湿地景观,周边栽植北方特有的菩提树之称的康段。"); break; case MENU_TYHJ: mapController.animateTo(position[2]); textView.setText("有热带海滩花岗岩风景区、购物区和度假村组成集成热带海洋风光...."); break; } return super.onOptionsItemSelected(item); } /* * 用于判断服务器是否显示远程信息,如驾车导航,是MapActivity类中必须实现的子类; 返回true则显示远程信息,否则不显示 */ @Override protected boolean isRouteDisplayed() { // TODO Auto-generated method stub return false; } /* * //该方法是将我们定义好的图层覆盖在MapView上1、得到MapView中的图层,增加的图标以及图标的位置 * 2、得到我们自定义的图层(myLocationOverlay、positionOverlay)并处理定位功能调用的动作 * 3、将我们得到的图层放到MapView地图中 */ public void getView(MapView map) { // 得到MapView中的图层 List<Overlay> overlays = mapView.getOverlays(); // 增加图标 Drawable drawable = getResources().getDrawable(R.drawable.star_big_on); // 设置图标的位置 drawable.setBounds(-drawable.getMinimumWidth(), -drawable.getMinimumHeight(), 0, 0); /* * myLocationOverlay对象调用其runOnFirstFix() * 方法并传入runnable参数,在其run方法中,去定义每次更新时执行程序块中的动作 * 将MapView设置成一般地图试图模式、修改地图缩放成度和目标地点移动等动作 */ myLocationOverlay = new MyLocationOverlay(this, mapView); myLocationOverlay.runOnFirstFix(new Runnable() { @Override public void run() { // TODO Auto-generated method stub mapView.setTraffic(true); mapController.setZoom(17); mapController.animateTo(myLocationOverlay.getMyLocation()); } }); overlays.add(myLocationOverlay); pOverlay = new positionOverlay(drawable); overlays.add(pOverlay); } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); myLocationOverlay.enableMyLocation(); } @Override protected void onStop() { // TODO Auto-generated method stub super.onStop(); myLocationOverlay.disableMyLocation(); } // 对话框显示 public void showDialog(String str) { new AlertDialog.Builder(ScenicDisplayActivity.this).setTitle("景点简介") .setMessage(str) .setNegativeButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub dialog.dismiss(); } }).show(); } /* * ItemizedOverlay:此类可以让我们在地图上添加多条信息,不同的信息可以添加到不同的 * 图层上,图层与图层之间可以重叠,就想我们在绘制游戏背景一样,最里面那层是背景层,接着可以绘制多个元素在外层,然后将外层重叠在背景层上。 */ private class positionOverlay extends ItemizedOverlay<OverlayItem> { private final int mRadius = 5; // 定义一个列表、用于装我们所要规定的旅游风景区 private List<OverlayItem> items = new ArrayList<OverlayItem>(); // 在地图上添加图标 public positionOverlay(Drawable drawable) { super(drawable); // TODO Auto-generated constructor stub items.add(new OverlayItem(position[0], "中国长城八达岭", "21度")); items.add(new OverlayItem(position[1], "中国首批A5", "16度")); items.add(new OverlayItem(position[2], "中国名旅", "32度")); populate();// 处理positionOverlay类之后所需要的动作 } // 根据编号抓取对应的"OverlayItem"对象返回,创建出图标的实体 @Override protected OverlayItem createItem(int i) { // TODO Auto-generated method stub return items.get(i); } // "size"返回这个图层告知总共包含了多少个图标 @Override public int size() { // TODO Auto-generated method stub return items.size(); } /* * 该方法用于处理用户所单击的图标的业务处理,如果不实现其方法,用户单击到图标时就不会有任何特殊的反应 */ @Override protected boolean onTap(int index) { // TODO Auto-generated method stub Toast.makeText(ScenicDisplayActivity.this, "今天的气温是" + items.get(index).getSnippet(), Toast.LENGTH_SHORT).show(); return super.onTap(index); } /* * 1、canvas:用来绘制的画布2、mapView:需要被标记的MapView * 3、shadow:为true则需要绘制阴影图层,否则需要绘制内容图层 */ @Override public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) { // TODO Auto-generated method stub /* * Projection类代表了MapView上得视窗坐标与经纬度坐标的映射关系,用MapView * 对象调用getProjection()就可以得到该地图的projection对象 */ Projection projection = mapView.getProjection(); for (int index = size() - 1; index >= 0; index--) { // 遍历,取得对应图标 OverlayItem overlayItem = getItem(index); // 获取图标的标题 String title = overlayItem.getTitle(); /* * //Projection对象调用toPixels()方法得到由风景点得 经纬度地理坐标转化为在地图上的投影位置 */ Point point = projection.toPixels(overlayItem.getPoint(), null); // 坐标圆形 RectF oval = new RectF(point.x - mRadius, point.y - mRadius, point.x + mRadius, point.y + mRadius); Paint paint = new Paint(); paint.setColor(Color.CYAN); paint.setAntiAlias(true); paint.setFakeBoldText(true); // 红色圆形 Paint backpaint = new Paint(); backpaint.setColor(Color.RED); backpaint.setAntiAlias(true); RectF backRectF = new RectF(point.x + 40 + mRadius, point.y - 3 * mRadius, point.x + 65, point.y + 2 * mRadius); // 文字设置 Paint paintText = new Paint(); paintText.setColor(Color.BLUE); paintText.setTextSize(25); canvas.drawOval(oval, paint); canvas.drawRoundRect(backRectF, 10, 10, backpaint); canvas.drawText(title, point.x, point.y, paintText); } return super.draw(canvas, mapView, shadow, when); } } }
在程序中,注释已经很清楚了,在此就不多说了。
7、最后别忘记在AndroidManifest.xml文件中添加权限如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.wyf.wpf" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".ScenicDisplayActivity" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <uses-library android:name="com.google.android.maps" /> </application> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="com.google.android.maps" /> </manifest>
8、结束