第14章__android个人轨迹跟踪器

第14章 个人轨迹跟踪器

本章中我们将编写一个个人的轨迹跟踪器,用它可以记录你外出旅游经过的各个地点,并在地图上画出轨迹,同时还可以查看经过的距离,平均的移动速度等等。要实现该软件我们要用到以下几个方面的知识:\r数据库存储操作;\rGoogleMap的开发;\rService的使用;\rActivity和MapActivity的使用;\r一些核心的算法,如通过两个经纬度坐标点求出他们之间的距离。

14.1 界面UI实现

一般情况下实现一个软件的基本流程如下:\r(1)设计各个Activity的界面并实现;\r(2)实现数据库的设计和实现;\r(3)实现程序的功能以及各个Activity的连接;\r(4)测试各功能效果。\r本节的工作就是完成第一步,实现界面UI。

14.1.1 界面规划

由软件功能我们大略地可以做出如下设计:\r主界面:进入主界面中,用户可以选择新建跟踪或查看已有跟踪,当然,也可以退出程序。\r新建跟踪界面:我们需要两个编辑框以方便用户输入相关信息,如新建的跟踪名、新建的跟踪的描述。当然,我们还需要添加一个确定按钮,单击该按钮后用户进入地图界面。\r已有跟踪界面:如果用户查看已有跟踪,我们需要一个列表,列出数据库中所有的跟踪记录,尽可能详尽地列出所有相关信息,单击其中的任意一条记录同样进入地图界面。

地图界面:首先要显示Google地图,接着还需分为两种状况:(1)如果是从查看已有跟踪界面跳转过来的,则在地图上显示曾经经历的轨迹。(2)如果是从新建跟踪界面跳转过来的则不做其他操作,等待消息更新界面。\r综上所述,程序总共需要4个Activity,关于主界面,这里就偷懒使用了第13章中的登陆界面,只需修改一下背景图片,这里就不再给出详细代码了,最后显示效果如图所示。\r

14.1.2 实现新建跟踪界面

新建跟踪时,我们在界面中需要与用户交互的组件主要有3个,分别是两个编辑框和一个按钮。一个编辑框用来给用户输入跟踪名,另一个编辑框用来输入跟踪描述,如“从A点到B点”。按钮则是用来确定用户是否编辑完毕,并跳转到地图跟踪界面。该界面比较简单,只需在一个LinearLayout中添加组件就可以了,主要代码如下所示:\r

\r\randroid:orientation="vertical"\r >\r \r

\r\r\r\randroid:text="跟踪名:"\r android:textSize="25sp"\r />\r \r \r

\r\r\r\r

\r\r\r\randroid:text="从A点到B点"\r />\r \r\r\r\r\r

最后的显示效果应当如下图所示,当然如果你觉得黑色的背景太单调了,那么也可以添加一些背景。\r \r

14.1.3 实现已有跟踪界面

在已有跟踪界面中,主要的组件就是一个ListView,当然,还需要每个Item的布局。所以该Activity的界面显示需要两个xml文件,让我们首先完成tracklist.xml文件的编写:\r

>\r \r\r\r\r\r

接着,我们完成每个Item的布局,每个选项我们希望显示的信息包括:\r(1)跟踪名,这时最主要的信息;\r(2)跟踪描述,用来显示该记录的相关描述;\r(3)开始时间,当本次跟踪的GPS开始工作时会记录开始时间;\r(4)结束时间,当用户推出软件时默认结束跟踪,记录结束时间;\r(5)距离,显示本次跟踪一共记录行径的里程数;\r(6)速度,用距离除以持续的时间,我们可以得到一个平均值,该值就是平均速度;\r(7)记录点数:显示本次记录一共记录了多少个GeoPoint点。

\r\r\r所以list_item.xml的代码如下所示:\r\r

\r\r\randroid:layout_height="wrap_content"\r android:paddingLeft="10dp"\r />\r \r 。。。。。。 //省略了其他5个TextView的显示,他们唯一不同的属性就是id \r\r\r

现在还看不出界面的布局效果,不过为了本节内容的完整,也使大家更直观,这里先给出运行效果如下图所示。\r\r\r\r\r

14.1.4 实现地图显示界面

地图界面中除了最主要的组件MapView之外,我们还需要一些其他的组件来丰富地图功能:\r(1)地址输入框,用来输入地址;\r(2)查询按钮,单击后开始查询该地址,并将地图中心设为该点;\r(3)我的位置按钮,单击后地图返回到我的位置;\r(4)放大按钮,单击后放大地图;\r(5)缩小按钮,单击后缩小地图。

最终的track..xml文件代码如下:\r\r \r

\r \r \r\r \r

\r\r\r/>\r \r \r

\r\r\randroid:orientation="horizontal"\r android:layout_gravity="bottom|right">\r \r

\r\r\randroid:id="@ id/zoomin"\r android:layout_width="50dp"\r android:layout_height="wrap_content"\r android:text="close"\r android:paddingRight="5dp"\r />\r

android:layout_height="wrap_content"\r android:text="far"\r android:paddingLeft="5dp"\r />\r \r\r这里还需提醒读者的是:(1)MapView的apiKey属性必须填写;(2)MapView的clickable属性需要为true,否则地图无法拖曳,最后效果显示如下图所示:\r\r\r

14.2 数据库实现

按照功能需求,我们需要在数据库中设计两张表,一张表用来记录所有的GeoPoint点的相关信息,我们将之命名为geopoints;另一张表用来记录每条记录的信息,命名为tracks。通过这样的设计,当用户要查看已有跟踪时只需查询tracks表,而地图显示要绘制路径时,只需查询geopoints表。

14.2.1 设计表结构

表geopoints中需要列出所有的点,需要包含的信息有ID、经度、纬度、创建时间、所属跟踪名。所以我们将表设计如下表所示:

表tracks中需要列出所有的跟踪记录,需要包含的信息有ID、跟踪名、跟踪描述、开始时间、结束时间、距离、速度、跟踪点数,所以表结构如下表所示:\r\r

14.2.2 实现DatabaseHelper

根据以上两张表的分析,我们可以实现DatabaseHelper的编写。关于SQLiteOpenHelper不知道读者是否已经掌握。如果还有疑惑,请翻阅本书的第九章——Android中的数据存储。最后的DatabaseHelper的代码如下所示:\rimport android.content.Context;\rimport android.database.sqlite.SQLiteDatabase;\rimport android.database.sqlite.SQLiteOpenHelper;\rpublic class DatabaseHelper extends SQLiteOpenHelper\r{

final static String DATABASENAME = "my_database.db";\rfinal static int VERSION = 1;\rfinal static String TABLENAME = "geopoints";\rfinal static String GEO_ID = "id";\rfinal static String GEO_LATITUDE = "latitude";\rfinal static String GEO_LONGITUDE = "longitude";\rfinal static String GEO_TIME = "time";\rfinal static String GEO_TRACKNAME = "trackname";\rfinal static String TABLENAME_2 = "tracks";\rfinal static String TRACK_ID = "id";\r

final static String TRACK_CREATETIME = "createtime";\rfinal static String TRACK_NAME = "name";\rfinal static String TRACK_DESC = "description";\rfinal static String TRACK_DIST = "distance";\rfinal static String TRACK_SPEED = "speed";\rfinal static String TRACK_COUNT = "count";\rfinal static String TRACK_ENDTIME = "endtime";\rpublic DatabaseHelper(Context context)\r{\rsuper(context, DATABASENAME, null, VERSION);\r// TODO Auto-generated constructor stub\r}\r

@Override\rpublic void onCreate(SQLiteDatabase db)\r{\r//实现geopoints表\rString sql = "CREATE TABLE " \rTABLENAME "(" \rGEO_ID " INTEGER PRIMARY KEY AUTOINCREMENT," \rGEO_LATITUDE " TEXT," \rGEO_LONGITUDE " TEXT," \rGEO_TRACKNAME " TEXT," \rGEO_TIME " TEXT);";\rdb.execSQL(sql);\r//实现tracks表\rString sql2 = "CREATE TABLE " \rTABLENAME_2 "(" \rTRACK_ID " INTEGER PRIMARY KEY AUTOINCREMENT,"

TRACK_NAME " TEXT," \rTRACK_CREATETIME " TEXT," \rTRACK_DESC " TEXT," \rTRACK_DIST " TEXT," \rTRACK_SPEED " TEXT," \rTRACK_COUNT " TEXT," \rTRACK_ENDTIME " TEXT);";\rdb.execSQL(sql2);\r}\r\r@Override\rpublic void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2)\r{\r// TODO Auto-generated method stub\r\r}\r}\r\r\r\r

14.3 功能实现

设计好数据库以后,我们就可以实现本应用的功能了。本例中使用了服务来完成核心工作,我们将之命名为TrackService。它一旦开始运行后就在后台不停地读取位置信息,读取到的位置信息会写入到数据库中,并通知Activity进行相关的界面修改。在地图上也就是TrackerActivity中画出所有经历过的点,并用线将他们连接起来,这条线就是我们的轨迹了。

14.3.1 实现TrackService

本例中的Service是需要一直运行在后台的,并不需要与Activity绑定,所以在实现时,我们只需重写其中的onCreate()、onStart()以及onDestroy()三个方法。\r在onCreate()方法中,所以其整体结构应当如下所示:\r1.整体结构\rimport 。。。。。。 //省略部分导入\rimport android.app.Service;\rimport android.database.sqlite.SQLiteDatabase;\rimport android.location.Criteria;\rimport android.location.LocationListener;\rimport android.location.LocationManager;\r

public class TrackService extends Service\r{\rpublic static final int MSG = 1;\rprivate DatabaseHelper helper;\rprivate SQLiteDatabase db;\rprivate LocationManager manager;\rprivate String locationProvider;\rprivate LocationListener locationListener;\rprivate long distance = 0;\rprivate long speed = 0;\rprivate long endTime;\rprivate long createTime = 0;\rprivate List list = new ArrayList();\r

@Override\rpublic IBinder onBind(Intent intent)\r{\rreturn null;\r}\r\r@Override\rpublic void onCreate()\r{\rcreateTime = System.currentTimeMillis(); //得到开始时间\r//此处完成SQLiteDatabas、LocationListener、LocationManager、LocationProvider的初始化工作\rsuper.onCreate();\r}\r

@Override\rpublic void onStart(Intent intent, int startId)\r{\r //开始监听位置变化信息\r manager.requestLocationUpdates(locationProvider, 1000, 10, locationListener);\r super.onStart(intent, startId);\r}\r@Override\rpublic void onDestroy()\r{\rendTime = System.currentTimeMillis(); //得到结束时间\rinsertTrackInfo(); //添加到数据库中\rsuper.onDestroy();\r}\r}

\r2. 完成onCreate()方法\r接下来我们就完成onCreate()方法中需要完成的各类初始化工作。其代码如下:\r@Override\rpublic void onCreate()\r{\rhelper = new DatabaseHelper(getBaseContext());\rdb = helper.getWritableDatabase();\r//得到LocationManager对象\r manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);\r //得到提供商\r locationProvider = getLocationProvider(manager);\r

\r\r\r//新建位置监听器\r locationListener = new LocationListener()\r {\r @Override\r public void onLocationChanged(Location location)\r {\r Message msg = TrackerActivity.trackHandler.obtainMessage();\r msg.what = MSG;\r msg.arg1 = list.size();\r msg.obj = location;\r

\r\r\r\rTrackerActivity.trackHandler.sendMessage(msg);\r insertGeoPoint(location); //保存到数据库\r }\r \r @Override\r public void onStatusChanged(String provider, int status, Bundle extras)\r {\r }\r \r

@Override\r public void onProviderEnabled(String provider)\r {\r }\r \r @Override\r public void onProviderDisabled(String provider)\r {\r }\r };\r \rsuper.onCreate();\r}

3. 完成getLocationProvider()方法\r其中,getLocationProvider()方法如下。该方法在第12章中已经有过讲解,如果有疑问请参照第12章一起阅读。\rpublic String getLocationProvider(LocationManager lm)\r{\r//新建一个选择提供商的标准\rCriteria cri = new Criteria();\r//设置精确度为精确,还可以设置为粗略ACCURACY.COARSE\rcri.setAccuracy(Criteria.ACCURACY_FINE);\r//设置耗电等级为低\rcri.setPowerRequirement(Criteria.POWER_LOW);\r//设置是否向用户收费\rcri.setCostAllowed(true);

\r\r\r\r//设置是否需要海拔信息\rcri.setAltitudeRequired(false);\r//设置是否需要方位信息\rcri.setBearingRequired(false);\r//得到最好的内容提供商\rString lp = lm.getBestProvider(cri, true);\rreturn lp;\r}\r

4. 完成insertGeoPoint()方法\r在locationListener中,每当得到一个新的Location时,我们需要执行两个操作:\r(1)将location信息交给TrackerActivity;\r(2)将location信息写入数据库。\r实现第一步我们使用了Handler机制,而实现第二步则是调用了insertGeoPoint()方法,该方法代码如下所示:\rpublic long insertGeoPoint(Location location)\r{\rContentValues values = new ContentValues();\rvalues.put(DatabaseHelper.GEO_LATITUDE, location.getLatitude());

\r\r\r\rvalues.put(DatabaseHelper.GEO_LONGITUDE, location.getLongitude());\rvalues.put(DatabaseHelper.GEO_TIME, System.currentTimeMillis());\rvalues.put(DatabaseHelper.GEO_TRACKNAME, NewTrackActivity.name);\rlong rowId = db.insert(DatabaseHelper.TABLENAME, null, values);\rreturn rowId;\r}\r

5. 完成insertTrackInfo()方法\r在Service结束时,也就是onDestroy()方法结束时,我们需要将本次记录的相关信息保存到tracks表中,方法如下:\rpublic long insertTrackInfo()\r{\rContentValues values = new ContentValues();\rvalues.put(DatabaseHelper.TRACK_NAME, NewTrackActivity.name);\rvalues.put(DatabaseHelper.TRACK_DESC, NewTrackActivity.desc);\rvalues.put(DatabaseHelper.TRACK_CREATETIME, createTime);

\r\r\r\rvalues.put(DatabaseHelper.TRACK_ENDTIME, endTime);\rvalues.put(DatabaseHelper.TRACK_COUNT, list.size());\rvalues.put(DatabaseHelper.TRACK_DIST, getDistance());\rvalues.put(DatabaseHelper.TRACK_SPEED, getSpeed());\rlong rowId = db.insertOrThrow(DatabaseHelper.TABLENAME_2, null, values);\rLog.i("TAG",String.valueOf(rowId));\rreturn rowId;\r}

14.3.2 实现OldTrackActivity

OldTrackActivity功能主要有两个:\r(1)显示已有跟踪;\r(2)单击任意跟踪,进入地图界面。

1. 整体设计\r我们首先完成其整体的结构设计,整体设计中最主要的工作就是界面的初始化:\rImport …… //省略部分导入\rimport java.text.DateFormat;\rimport java.util.Date;\rimport android.database.Cursor;\rimport android.database.sqlite.SQLiteDatabase;\rimport android.widget.AdapterView.OnItemClickListener;\rimport android.widget.ListAdapter;\rimport android.widget.ListView;\r

\r\r\rpublic class OldTrackActivity extends Activity\r{\rpublic static String name = null;\rpublic static String desc = null;\rpublic static int createTime = 0;\rListView lv;\rSQLiteDatabase db;\r@Override\rprotected void onCreate(Bundle savedInstanceState)\r{

super.onCreate(savedInstanceState);\rsetContentView(R.layout.tracklist);\r//初始化数据库\rDatabaseHelper helper = new DatabaseHelper(getBaseContext());\rdb = helper.getReadableDatabase();\r//初始化界面\rinitView();\r}\r}\r

2. 完成initView()\rInitView()方法中实现了所有的界面初始化,和单击事件的监听。其中的getData()方法得到了要显示的数据源,通过该数据源可以产生一个适配器,使用该适配器就可以将数据与界面很好地绑定了。\rpublic void initView()\r{\rlv = (ListView) findViewById(R.id.lv);\rfinal ArrayList> data = getData(); //得到数据源\rListAdapter adapter = new SimpleAdapter(getBaseContext(), //得到适配器\rdata, \rR.layout.list_item, \rnew String[]{"trackName","trackDesc","trackBegTime","trackEndTime","trackDist","trackSpeed","trackCount"},

new int[]{R.id.tv1,R.id.tv2,R.id.tv3,R.id.tv4,R.id.tv5,R.id.tv6,R.id.tv7,});\rlv.setAdapter( adapter); //设置适配器\r//监听单击事件\rlv.setOnItemClickListener(new OnItemClickListener()\r{\r@Override\rpublic void onItemClick(AdapterView parent, View view, int position, long id)\r{\rString trackName = data.get(position).get("trackName");

Intent i = new Intent(OldTrackActivity.this,TrackerActivity.class);\ri.putExtra("trackName", trackName);\rstartActivity(i);\r}\r});\r}\r

3. 完成getData()\r该方法可以说是本Activity的核心了,其工作是查询数据库得到返回的Cursor对象,然后逐条遍历将之重新构造为我们需要的数据结构。\rpublic ArrayList> getData()\r{\rArrayList> listItems= new ArrayList>();\rCursor cursor = db.query(DatabaseHelper.TABLENAME_2, \rnull,null,null,null, null, null);\r//cursor读取第一条记录\rcursor.moveToFirst();\rwhile(!cursor.isAfterLast())\r{

\r\r\r//读取所有存储的内容\rString trackId = cursor.getString(cursor.getColumnIndex(DatabaseHelper.TRACK_ID));\rString trackName = cursor.getString(cursor.getColumnIndex(DatabaseHelper.TRACK_NAME));\rString trackDesc = cursor.getString(cursor.getColumnIndex(DatabaseHelper.TRACK_DESC));\rlong trackBegTime = cursor.getLong(cursor.getColumnIndex(DatabaseHelper.TRACK_CREATETIME));

long trackEndTime = cursor.getLong(cursor.getColumnIndex(DatabaseHelper.TRACK_ENDTIME));\rString trackDist = cursor.getString(cursor.getColumnIndex(DatabaseHelper.TRACK_DIST));\rString trackSpeed = cursor.getString(cursor.getColumnIndex(DatabaseHelper.TRACK_SPEED));\rString trackCount = cursor.getString(cursor.getColumnIndex(DatabaseHelper.TRACK_COUNT));\r

\r\r//将内容重新组织结构\rDate begDate = new Date(trackBegTime);\rString trackBegDate = DateFormat.getDateTimeInstance().format(begDate);\rDate endDate = new Date(trackEndTime);\rString trackEndDate = DateFormat.getDateTimeInstance().format(endDate);\rHashMap map = new HashMap();\rmap.put("trackId",trackId);\rmap.put("trackName","跟踪名: " trackName);\rmap.put("trackDesc","描述:" trackDesc);\rmap.put("trackBegTime","开始时间:" trackBegDate);

map.put("trackEndTime","结束时间: " trackEndDate);\rmap.put("trackDist","距离:" trackDist " km");\rmap.put("trackSpeed","速度:" trackSpeed " km/h");\rmap.put("trackCount","记录点数: " trackCount);\r//添加到列表中\rlistItems.add(map);\r//读取下一个\rcursor.moveToNext();\r}\rreturn listItems;\r}\r\r\r\r\r\r\r\r

14.3.3 实现TrackerActivity

在TrackerActivity中我们首先完成地图的基本功能,如放大、缩小、切换视角等。\r1. 整体结构\rimport 。。。。。。 //省略部分导入\rimport com.google.android.maps.GeoPoint;\rimport com.google.android.maps.MapActivity;\rimport com.google.android.maps.Overlay;\rimport android.location.Geocoder;\rimport android.location.Location;\rimport android.location.LocationListener;

import android.location.LocationManager;\rpublic class TrackerActivity extends MapActivity {\r MapView mv;\r MapController controller;\r GeoPoint curPoint;\r Button b_zoomin;\r Button b_zoomout;\r Button b_myaddr;\r Button b_search;\r EditText et;\r TextView pop_content;\r View popView;\r public static Handler trackHandler;\r public static final int OLD_TRACK = 0;\r public static final int STREET_ID = 1; \r public static final int TRAFFIC_ID = 2;\r public static final int SATE_ID = 3; \r public static final int DEFAULT_ID = 4;\r \r

@Override\r public void onCreate(Bundle savedInstanceState) {\r super.onCreate(savedInstanceState);\r setContentView(R.layout.track);\r //初始化界面\r initView();\r //新建handler\r trackHandler = new Handler()\r {\r@Override\rpublic void handleMessage(Message msg)\r{

//这里根据不同的消息进行界面的相关修改\r}\r };\r //判断是否从已有记录界面跳转过来\r String trackName = getIntent().getStringExtra("trackName");\r if (trackName != null)\r {\r Message msg = trackHandler.obtainMessage();\r msg.what = OLD_TRACK;\r msg.obj = trackName;\r trackHandler.sendMessage(msg);\r }\relse\r{

//开始服务,在后台记录地理位置的变更\r Intent i = new Intent(this,TrackService.class);\r startService(i);\r}\r }\r @Override\rprotected boolean isRouteDisplayed()\r{\r// TODO Auto-generated method stub\rreturn false;\r}\r//添加菜单项,增加各个视图\r@Override\r public boolean onCreateOptionsMenu(Menu menu) {\r super.onCreateOptionsMenu(menu);\r \r menu.add(0, TRAFFIC_ID, 0, "交通"); \r

menu.add(0, SATE_ID, 1, "卫星");\r menu.add(0, STREET_ID, 2, "街道");\r menu.add(0, DEFAULT_ID, 3, "默认");\r return true;\r }\r//增加视图的实现\r @Override\r public boolean onOptionsItemSelected(MenuItem item) {\r switch (item.getItemId()) { \r case TRAFFIC_ID:\r {\r mv.setTraffic(true);\r mv.setSatellite(false);\r mv.setStreetView(false);\r return true;\r }\r case SATE_ID:\r {\r

mv.setSatellite(true);\r mv.setStreetView(false);\r mv.setTraffic(false);\r return true;\r }\r case STREET_ID:\r {\r mv.setStreetView(true);\r mv.setSatellite(false);\r mv.setTraffic(false);\r return true;\r }\r case DEFAULT_ID:\r {\r mv.setStreetView(false);\r mv.setSatellite(false);\r mv.setTraffic(false);\r return true;\r

}\r \r }\r return super.onOptionsItemSelected(item);\r }\r2. 实现handler\rHandler需要执行两种操作:\r(1)如果消息来自Service,也就是其类型为TrackService.MSG,则在地图上画出该点。\r(2)如果消息的类型是OLD_TRACK,则在地图上将其轨迹画出。\r@Override\rpublic void handleMessage(Message msg)\r{\rif (msg.what == TrackService.MSG)\r{\rint count = msg.arg1;\rString pointName = "位置" String.valueOf(count);\rLocation location = (Location) msg.obj;

//从Location中获得GeoPoint对象\rcurPoint = getGeoPoint(location);\r//将地图移动到该地点\rcontroller.animateTo(curPoint);\r//移除之前的pop\rmv.removeView(popView);\r//设置popView的属性\rpopView.setLayoutParams(new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT, curPoint, MapView.LayoutParams.BOTTOM_CENTER));\r//在气泡中要显示的内容\rpop_content.setText("我的位置");\r//将popView添加到地图中\rmv.addView(popView);\r//使用Overlay显示\rMyOverlay overlay = new MyOverlay(curPoint,pointName);\r//得到已有的Overlay列表

List overlays = mv.getOverlays();\r//将新建的Overlay加入到列表中显示\roverlays.add(overlay);\r}\relse if (msg.what == OLD_TRACK)\r{\r//得到已有的Overlay列表\rList overlays = mv.getOverlays();\rString trackName = (String) msg.obj;\rDatabaseHelper helper = new DatabaseHelper(getBaseContext());\rSQLiteDatabase db = helper.getWritableDatabase();\rCursor cursor = db.query(DatabaseHelper.TABLENAME, null, DatabaseHelper.GEO_TRACKNAME "=?", new String[]{trackName}, null, null, null); \rcursor.moveToFirst();\rwhile (!cursor.isAfterLast())\r{

int latitude = (int) (Double.parseDouble(cursor.getString(cursor.getColumnIndex(DatabaseHelper.GEO_LATITUDE)))*1E6);\rint longitude = (int) (Double.parseDouble(cursor.getString(cursor.getColumnIndex(DatabaseHelper.GEO_LONGITUDE)))*1E6);\rString geoId = cursor.getString(cursor.getColumnIndex(DatabaseHelper.GEO_ID));\rLog.i("TAG","id:" geoId);\rLog.i("TAG",String.valueOf(latitude) ":" String.valueOf(longitude));\rGeoPoint point = new GeoPoint(latitude,longitude);\rcontroller.animateTo(point);\r//使用Overlay显示\rMyOverlay overlay = new MyOverlay(point,"位置" geoId);\r//将新建的Overlay加入到列表中显示\roverlays.add(overlay);\rcursor.moveToNext();\r}\r\r\r\r\r\r\r\r\r\r

cursor.close();\rdb.close();\r}\rsuper.handleMessage(msg);\r}\r3. 实现initView()\rinitView()方法中实现了组件的初始化,以及各个按钮的单击事件的实现。这里仅仅给出了各个单击事件的相关代码,省略了各个组件的findViewById()方法,相信大家可以自行完成。如有问题请参照源代码。\rpublic void initView()\r{\r//实例化pop布局\rLayoutInflater inflater = LayoutInflater.from(this);\rpopView = inflater.inflate(R.layout.pop, null);\rpopView.setOnClickListener(new OnClickListener()\r{\r@Override

public void onClick(View v)\r{\rv.setVisibility(View.GONE);\r}\r});\r。。。。。。 //此处省略了各个组件实例化\r//得到MapView的控制器\r controller = mv.getController();\r //设置地图缩放等级,参数的值在1到21之间\r controller.setZoom(10);\r OnClickListener l = new OnClickListener()\r{\r@Override\rpublic void onClick(View v)\r{\rint id = v.getId();\rswitch(id)\r{

case R.id.zoomin:\r{\rzoomIn();\rbreak;\r}\rcase R.id.zoomout:\r{\rzoomOut();\rbreak;\r}\rcase R.id.myaddr:\r{\rif (curPoint != null)\r{\rcontroller.animateTo(curPoint);\r}\relse\r{

Toast.makeText(getBaseContext(), "暂时还未获得您的位置", Toast.LENGTH_SHORT).show();\r}\rbreak;\r}\rcase R.id.search:\r{\rString addrName = et.getText().toString();\rGeoPoint point = getGeoPointByAddr(addrName);\rcontroller.animateTo(point);\rcontroller.setZoom(20);\rbreak;\r}\r}\r}\r};\r//设置各个按钮的单击事件\rb_zoomin.setOnClickListener(l);

b_zoomout.setOnClickListener(l);\rb_myaddr.setOnClickListener(l);\rb_search.setOnClickListener(l);\r}\r4. 实现地理位置与经纬度坐标的转换\r一般所有地图都有查询功能,该功能需要将经纬度转换为实际的地名,这样用户更直观;同时也需要将实际地名转换为经纬度坐标点,这样地图才知道怎样显示。为了以后使用方便,这里写了两个方法:\r(1)getAddrByGeoPoint(),用来将GeoPoint对象转换为实际地址;\r(2)getGeoPointByAddr(),用来将实际地址反转为GeoPoint对象。\r这两个方法的实现代码如下:\rpublic String getAddrByGeoPoint(GeoPoint point)\r{\rString addr = "";\rtry\r{

//新建解码器\rGeocoder coder = new Geocoder(this,Locale.getDefault());\r//得到经纬度\rdouble latitude = point.getLatitudeE6()/1E6;\rdouble longitude = point.getLongitudeE6()/1E6;\r//得到地址\rList

list = coder.getFromLocation(latitude, longitude, 1);\rStringBuilder builder = new StringBuilder();\rif (list.size() > 0)\r{\rAddress address = list.get(0);\rfor(int i = 0; i < address.getMaxAddressLineIndex();i )\r{\rbuilder.append(address.getAddressLine(i) "\n");\r}

//得到国家\rbuilder.append("国家: " address.getCountryName());\raddr = builder.toString();\rLog.i("TAG", addr);\r}\relse\r{\rToast.makeText(getBaseContext(), "无法解析到地名", Toast.LENGTH_SHORT).show();\r}\r}\rcatch (IOException e)\r{\r// TODO Auto-generated catch block\re.printStackTrace();\r}\rreturn addr;\r}\r

public GeoPoint getGeoPointByAddr(String addr)\r{\rGeoPoint point = null;\rtry\r{\r//新建解码器\rGeocoder coder = new Geocoder(this,Locale.getDefault());\r//得到地址\rList

list = coder.getFromLocationName(addr, 1);\rif (list.size() > 0)\r{\rAddress address = list.get(0);\r//得到经纬度\rdouble latitude = address.getLatitude()*1E6;\rdouble longitude = address.getLongitude()*1E6;\r//得到GeoPoint对象

point = new GeoPoint((int)latitude,(int)longitude);\r}\relse\r{\rToast.makeText(getBaseContext(), "无法解析地名", Toast.LENGTH_SHORT).show();\r}\r}\rcatch (IOException e)\r{\r// TODO Auto-generated catch block\re.printStackTrace();\r}\rreturn point;\r}\r}\r\r\r\r\r\r\r\r\r

14.3.4 实现Overlay

Overlay是用来在地图上画各类图形的,为了能画出轨迹,我们需要在draw()方法中实现3个工作:\r(1)画出一个小红点,用来标注用户曾经经过的地点,主要方法为:\rcanvas.drawOval(rect,paint);\r(2)在小红点边上画出说明性文字和背景色,主要方法为:\rcanvas.drawRoundRect(backRect, 5, 5, backPaint); //画背景\rcanvas.drawText(content, point.x 10, point.y, textPaint); //写文字

(3)将这些小红点连接起来,形成个人的轨迹,主要方法为:\rcanvas.drawPath(path, paint);\r最后,MyOverlay的实现代码如下:\rimport android.graphics.Canvas;\rimport android.graphics.Color;\rimport android.graphics.Paint;\rimport android.graphics.Point;\rimport android.graphics.RectF;\rimport com.google.android.maps.GeoPoint;\rimport com.google.android.maps.MapView;\rimport com.google.android.maps.Overlay;\rimport com.google.android.maps.Projection;\r\rpublic class MyOverlay extends Overlay\r{\rlong latitude;\rlong longitude;

List list = new ArrayList();\rString content;\r\rpublic MyOverlay(long latitude,long longitude,String content)\r{\rthis.latitude = latitude;\rthis.longitude = longitude;\rthis.content = content;\r}\r\rpublic MyOverlay(GeoPoint point,String content)\r{\rthis.latitude = point.getLatitudeE6();\rthis.longitude = point.getLongitudeE6();\rthis.content = content;\r}\r\r@Override

public boolean draw(Canvas canvas, MapView mapview, boolean shadow, long when)\r{\r//新建投影数据对象\rProjection projection = mapview.getProjection();\r//新建GeoPoint对象\rGeoPoint geoPoint = new GeoPoint((int)latitude,(int)longitude);\r//新建Point对象\rPoint point = new Point();\r//将GeoPoint对象投影成可显示的Point对象\rprojection.toPixels(geoPoint,point);\rlist.add(point);\r//创建一个RectF对象\rRectF rect = new RectF(point.x-5,point.y-5,point.x 5,point.y 5);\r//新建颜料对象\rPaint paint = new Paint();\r//设置颜色为红色

paint.setColor(Color.RED);\r//在画布上将椭圆画出来\rcanvas.drawOval(rect,paint);\r\r//创建背景\rRectF backRect = new RectF(point.x 7,point.y-15,point.x 60,point.y 5);\r//新建背景的颜料对象\rPaint backPaint = new Paint();\r//设置字体颜色为白色\rbackPaint.setColor(Color.GRAY);\r//设置透明度\rbackPaint.setAlpha(100);\r//在画布上画出一个圆角矩形\rcanvas.drawRoundRect(backRect, 5, 5, backPaint);\r

//新建字体的颜料对象\rPaint textPaint = new Paint();\r//设置字体颜色为白色\rbackPaint.setColor(Color.WHITE);\r//将内容显示在画布上,坐标点要在背景之间\rcanvas.drawText(content, point.x 10, point.y, textPaint);\r//画出轨迹\rfor (int i = 0;i< list.size() - 1;i )\r{\r//得到前一个点\rGeoPoint oldGeoPoint = list.get(i);\rPoint oldPoint = new Point();\rprojection.toPixels(oldGeoPoint,oldPoint);\r//得到后一个点\rGeoPoint newGeoPoint = list.get(i 1);\rPoint newPoint = new Point();\r\r\r\r

projection.toPixels(newGeoPoint,newPoint);\r//将轨迹画出\rPath path = new Path(); \rpath.moveTo(oldPoint.x, oldPoint.y); \rpath.lineTo(newPoint.x, newPoint.y); \rcanvas.drawPath(path, paint); \r}\rreturn super.draw(canvas, mapview, shadow, when);\r}\r}\r\r

14.3.5 修改注册文件

到这里,主要的代码已经完成了,最后不要忘记在注册文件中添加相关权限、Activity信息以及Service信息。\r\r \r\r

\r \r \r \r \r \r

\r\r\r\r\r \r \r \r \r\r\r

\r现在,程序已经可以运行啦,运行时效果如下图所示:\r \r

14.4 小结

本章我们一起实现了一个LBS应用——个人轨迹记录器。通过本章读者可以巩固数据库、Service、Activity以及Google Maps等相关知识,同时熟悉一个应用从设计到实现的基本流程。本章的重点在与GoogleMaps API的熟练使用,难点是使用Overlay在地图上划出各类图形。