`
yuanlanjun
  • 浏览: 1184921 次
文章分类
社区版块
存档分类
最新评论

Android特色开发 Google Map

 
阅读更多


Android 是一个面向应用程序开发的丰富平台,它拥有许多具有吸引力的用户界面元素、数据管理和网络应用等优秀的功能。Android 还提供了很多颇具特色的接口。本章我们将分别介绍这些吸引开发者眼球的特色开发,主要包括:传感器系统(Sensor)、语音识别技术(RecognizerIntent)、Google Map和用来开发桌面的插件(Widget)。通过本章的学习,读者将对Android有一个更深入的了解,可以开发出一些有特色、有创意的应用程序。

9.3 Google Map

提起Google Map(Google地图),大家无不想到其姊妹产品Google Earth(Google地球)。全新的免费地图服务让Google在2005年震惊了整个互联网界。此后,各大门户纷纷推出自己的地图服务,不少门户还和Google一样提供了二次开发的API。目前,基于地图服务的各种应用已如雨后春笋般到处萌发了。当然,对于Google的Android系统来说,地图肯定也是必不可少的特色。

9.3.1 Google Map概述

Google Map是 Google 公司提供的电子地图服务,包括局部详细的卫星照片。它能提供三种视图:一是矢量地图(传统地图),可提供政区和交通以及商业信息;二是不同分辨率的卫星照片(俯视地图,与Google Earth 上的卫星照片基本一样);三是后来加上的地形视图,可以用以显示地形和等高线。它的姊妹产品是Google Earth——一个桌面应用程序,在三维模型上提供街景和更多的卫星视图及GPS定位的功能。

Google公司于2004年11月收购了美国Keyhole公司,推出了http://maps.google.com,令人耳目一新。但Google并未就此止步,在2005年6月底推出了桌面工具Google Earth,把“地球”放到了每个人的桌面上,让你坐在电脑前,就可以在名川大山间漫步,在摩天楼群中俯瞰。

当然,随着Google Map和Google Earth的诞生,也出现了很多非常有趣的应用,比如下面两个典型的基于Google Earth和Google Maps的小游戏非常有创意,吸引了不少玩家。

如图9-5所示,我们可以在全球任何地方甚至海底模拟开飞机或者潜水艇,来漫游整个世界,更多游戏请参见http://www.sea-seek.com/。

图9-5 模拟飞行

如图9-6所示,我们可以在地球上任何地方开着自己喜欢的车奔跑,非常有意思的是,可以在电脑面前开着车在自己周围以及熟悉的地方模拟驾驶。详细信息请参见http://geoquake.jp/en/ webgame/DrivingSimulatorGM/。

图9-6 模拟驾驶

类似的应用还有很多,这里我们只介绍这两款,有兴趣的朋友可以自己去试试。现在Google Map已经被应用到很多手机上了,这更加方便了大家的生活。下面我们来看看手机上如何应用Google Map,如图9-7所示。

图9-7 Google Map 手机版

它包括如下功能:

·我的位置(测试版):“我的位置”在地图上显示你的当前位置(通常在 1000 米范围之内)。即使没有 GPS,你也可以确定自己的位置。谷歌手机地图还支持内置 GPS,也可以链接到蓝牙 GPS 传感器,以便更准确地确定用户的位置。“我的位置”功能是通过识别你附近无线发射塔的信息广播而确定你的位置的。

·地图和卫星视图:谷歌手机地图向你提供所查看地区的地图和卫星视图,其界面的使用感觉与你在台式机上相同。 可沿其中一个方向滚动,以查看地图上的更多内容;或使用快捷键进行缩放。

·商户列表:借助于 Google 的本地搜索引擎,可以按名称(如“星巴克”)或类型(如“咖啡”)搜索商家,查看商店的营业时间和评分,然后,只需点击一下即可拨通感兴趣的商家的电话。有了“我的位置”功能,甚至都不需要输入当前位置即可方便地找到附近的商家。

·驾车路线:可以很方便地获得驾车路线,其中会清楚地标明每次转弯。有了“我的位置”功能,甚至都不需要输入出发点。

·公交换乘:查看公交和地铁线路,确定转车路线,制定你在全球 80 多个城市的出行计划。“公交换乘”功能目前适用于黑莓、Windows Mobile、S60 和其他支持 Java 的手机。

·路况信息:Google 地图中的公路会根据实时路况数据,以绿色、黄色或红色显示。

·收藏夹:为你常去的地方加上书签,以便能在地图上非常方便地返回到这些地方。

大家不要认为这些功能在手机上很难实现,尤其是在我们要学习的Android平台中,要实现这些功能是非常简单的,只需要使用Android Maps API(地图API)和Android Location API(定位API)即可。下面我们将学习如何来使用这些API开发自己的地图应用。

9.3.2 准备工作

在Android SDK?1.5预装的add-on中提供了一个Map扩展库com.google.android.maps,利用它就可以给android应用程序加上强大的地图功能了。这个库的位置是“Android SDK路径”\add-ons\google_apis-3\libs。需要说明的是,这个库并不是标准的Android sdk的内容,可以自己从这个位置下载,并放到你的sdk中,这样就可以为你新建的应用或者已有的应用加上地图功能了。在使用Android Map API之前,还需要申请一个Android Map API Key。

1.申请Android Map API Key

为了能顺利地申请Android Map API Key,必须要准备Google的账号和系统的证明书。一般Google发布Key都需要Google的账号,Google的账号是通用的,Gmail的账号就可以了(没有的话可以到http://www.google.com/去申请一个)。当一个应用程序发布时必须要证明书,证明书其实就是MD5。我们这里不是发布,而只是为了测试,可以使用Debug版的证明书。下面我们来学习如何申请Android Map API Key。

步骤1:找到你的debug.keystore文件。

证书的一般路径为:C:\Documents and Settings\当前用户\Local Settings\Application Data\ Android\debug.keystore。当然我们使用Eclipse开发,便可以打开Eclipse选择Windows→Preference→Android→Build,其中Default debug keystore的值便是debug.keystore的路径,如图9-8所示。

图9-8 debug.keystore文件的位置

步骤2:取得debug.keystore的MD5值。

首先在命令提示符下进入debug.keystore文件所在的路径,执行命令:keytool -list -keystore debug.keystore,这时可能会提示你输入密码,这里输入默认的密码“android”,即可取得MD5值,如图9-9所示。

图9-9 取得debug.keystore的MD5值

步骤3:申请Android Map的API Key。

打开浏览器,输入网址:http://code.google.com/intl/zh-CN/android/maps-api-signup.html,登录Google账号,在Google的Android Map API Key申请页面上输入步骤2得到的MD5认证指纹,选中“I have read and agree with the terms and conditions”选项,如图9-10所示,按下“Generate API Key”按钮,即可得到我们申请到的API Key。

图9-10 Android Map API Key申请页面

到这里我们便完成了Android Map API Key的申请,记下申请的Android Map API Key值,在后面的应用程序中会用到它。下面我们还将创建一个基于Google APIs的AVD。

2.创建基于Google APIs的AVD

在Eclipse中打开AVD管理界面,在“Create AVD”部分的Name处填写AVD的名字,在Target处选择“Google APIs-1.5”,如图9-11所示,点击“Create AVD”按钮完成创建。

图9-11 创建AVD

3.创建基于Google APIs的工程

这里需要注意的是,由于我们需要使用Google APIs,所以在创建工程时,在Build Target处需要选择Google APIs,如图9-12所示,其他选项和以前一样。当然,在运行工程时也就需要选择我们刚刚创建的基于Google APIs的AVD来运行。

图9-12 创建基于Google APIs的工程

到这里,我们基本完成了准备工作,下面我们将开始学习如何使用Google API来开发地图应用程序。

9.3.3 Google Map API的使用

Android中定义了一个名为com.google.android.maps的包,其中包含了一系列用于在Google Map上显示、控制和层叠信息的功能类,以下是该包中最重要的几个类:

·MapActivity:这个类是用于显示Google Map的Activity类,它需要连接底层网络。MapActivity是一个抽象类,任何想要显示MapView的activity都需要派生自MapActivity,并且在其派生类的onCreate()中,都要创建一个MapView实例。

·MapView:MapView是用于显示地图的View组件。它派生自android.view.ViewGroup。它必须和MapActivity配合使用,而且只能被MapActivity创建,这是因为MapView需要通过后台的线程来连接网络或者文件系统,而这些线程要由MapActivity来管理。

·MapController:MapController用于控制地图的移动、缩放等。

·Overlay:这是一个可显示于地图之上的可绘制的对象。

·GeoPoint:这是一个包含经纬度位置的对象。

下面我们将使用com.google.android.maps包来实现一个地图浏览程序(见本书所附代码:第9章\Examples_09_03)。

步骤1:创建工程,注意要选择的Build Target为“Google APIs”。

步骤2:修改AndroidManifest.xml文件。

由于我们要使用Google Map API,所以必须先在AndroidManifest.xml中定义如下信息:<uses-library android:name="com.google.android.maps"/>,当然要从网络获取地图数据,还需要添加应用程序访问网络的权限。代码如下:

<uses-library android:name="com.google.android.maps" />

步骤3:创建MapView。

要显示地图,需要创建一个MapView,在XML文件中的布局如代码清单9-3所示。其中android:apiKey的值便是我们申请的Android Map API Key。

代码清单9-3 main.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<com.google.android.maps.MapView

android:id="@+id/MapView01"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:apiKey="0dYpmIXGIdwiVm-HEpzuUW2fjNYsFQ9EvYir1sg"/>

</RelativeLayout>

当然,可以在程序中通过如下代码创建MapView。

MapView map = new MapView(this, "[Android Maps API Key]");

步骤4:实现MapActivity。

MapView需要由MapActivity来管理,所以程序部分应该继承自MapActivity类,必须实现isRouteDisplayed方法。MapView提供了3种模式的地图,分别可以通过以下方式设置采用什么模式来显示地图。另外,可以通过setBuiltInZoomControls方法设置地图是否支持缩放。

//设置为交通模式

//mMapView.setTraffic(true);

//设置为卫星模式

//mMapView.setSatellite(true);

//设置为街景模式

//mMapView.setStreetView(false)

步骤5:MapController的使用。

如果要设置地图显示的地点以及放大的倍数等,就需要使用MapController来控制地图。可以通过如下代码获得MapController对象:

mMapController = mMapView.getController();

要定位地点,需要构建一个GeoPoint来表示地点的经度和纬度,然后使用animateTo方法将地图定位到指定的GeoPoint上,代码如下:

//设置起点为成都

mGeoPoint=new GeoPoint((int)(30.659259*1000000),(int)(104.065762*1000000));

//定位到成都

mMapController.animateTo(mGeoPoint);

步骤6:Overlay的使用。

如果需要在地图上标注一些图标文字等信息,就需要使用Overlay。这里我们首先要将地图上的经度和纬度转换成屏幕上实际的坐标,才能将信息绘制上去。Map API中提供了Projection.toPixels(GeoPoint in, Point out)方法,可以将经度和纬度转换成屏幕上的坐标。首先需要实现Overlay中的draw方法才能在地图上绘制信息,代码如下:

class MyLocationOverlay extends Overlay

{

public boolean draw(Canvas canvas,MapView mapView,boolean shadow,long when)

{

//...

}

}

下面是示例运行效果,图9-13以交通模式显示地图,图9-14以卫星模式显示地图,它们都在屏幕上显示了一个图标,并标明了位置。

图9-13 交通模式地图

图9-14 卫星模式地图

该示例显示了成都市区的地图,标注了天府广场的大概位置,具体实现如代码清单9-4所示。

代码清单9-4 第9章\Examples_09_03\src\com\yarin\android\Examples_09_03\Activity01.java

public class Activity01 extends MapActivity

{

private MapView mMapView;

private MapController mMapController;

private GeoPoint mGeoPoint;

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

mMapView = (MapView) findViewById(R.id.MapView01);

//设置为交通模式

//mMapView.setTraffic(true);

//设置为卫星模式

mMapView.setSatellite(true);

//设置为街景模式

//mMapView.setStreetView(false);

//取得MapController对象(控制MapView)

mMapController = mMapView.getController();

mMapView.setEnabled(true);

mMapView.setClickable(true);

//设置地图支持缩放

mMapView.setBuiltInZoomControls(true);

//设置起点为成都

mGeoPoint=new GeoPoint((int)(30.659259*1000000),(int)(104.065762*1000000));

//定位到成都

mMapController.animateTo(mGeoPoint);

//设置倍数(1-21)

mMapController.setZoom(12);

//添加Overlay,用于显示标注信息

MyLocationOverlay myLocationOverlay = new MyLocationOverlay();

List<Overlay> list = mMapView.getOverlays();

list.add(myLocationOverlay);

}

protected boolean isRouteDisplayed()

{

return false;

}

class MyLocationOverlay extends Overlay

{

public boolean draw(Canvas canvas,MapView mapView,boolean shadow,long when)

{

super.draw(canvas, mapView, shadow);

Paint paint = new Paint();

Point myScreenCoords = new Point();

// 将经纬度转换成实际屏幕坐标

mapView.getProjection().toPixels(mGeoPoint,myScreenCoords);

paint.setStrokeWidth(1);

paint.setARGB(255, 255, 0, 0);

paint.setStyle(Paint.Style.STROKE);

Bitmap bmp = BitmapFactory.decodeResource(getResources(),

R.drawable.home);

canvas.drawBitmap(bmp,myScreenCoords.x,myScreenCoords.y,paint);

canvas.drawText("天府广场",myScreenCoords.x, myScreenCoords.y,

paint);

return true;

}

}

}

9.3.4 定位系统

全球定位系统(Global Positioning System,GPS)又称为全球卫星定位系统,是一个中距离圆型轨道卫星导航系统,它可以为地球表面的绝大部分地区(98%)提供准确的定位、测速和高精度的时间标准。该系统由美国国防部研制和维护,可满足位于全球任何地方或近地空间的军事用户连续、精确地确定三维位置、三维运动和时间的需要。该系统包括太空中的24颗GPS卫星,地面上的1个主控站、3个数据注入站和5个监测站及作为用户端的GPS接收机。最少只需其中3颗卫星,就能迅速确定用户端在地球上所处的位置及海拔高度。所能连接到的卫星数越多,解码出来的位置就越精确。GPS广泛应用于军事、物流、地理、移动电话、数码相机、航空等领域,具有非常强大的功能,主要包括:

·精确定时:广泛应用在天文台、通信系统基站、电视台中。

·工程施工:道路、桥梁、隧道的施工中大量采用GPS设备进行工程测量。

·勘探测绘:野外勘探及城区规划中都有用到。

·导航。

···武器导航:精确制导导弹、巡航导弹。

···车辆导航:车辆调度、监控系统。

···船舶导航:远洋导航、港口/内河引水。

···飞机导航:航线导航、进场着陆控制。

···星际导航:卫星轨道定位。

···个人导航:个人旅游及野外探险。

·定位。

···车辆防盗系统。

···手机、PDA、PPC等通信移动设备防盗以及电子地图、定位系统。

···儿童及特殊人群的防走失系统。

·精准农业:农机具导航、自动驾驶以及土地高精度平整。

Android 支持地理定位服务的API。该地理定位服务可以用来获取当前设备的地理位置,应用程序可以定时请求更新设备当前的地理定位信息。比如应用程序可以借助一个Intent接收器来实现如下功能:以经纬度和半径划定一个区域,当设备出入该区域时,发出提醒信息,还可以和Google Map API一起使用,完成更多的任务。关于地理定位系统的API全部位于android.location包内,其中包括以下几个重要的功能类:

·LocationManager:本类提供访问定位服务的功能,也提供获取最佳定位提供者的功能。另外,临近警报功能也可以借助该类来实现。

·LocationProvider:该类是定位提供者的抽象类。定位提供者具备周期性报告设备地理位置的功能。

·LocationListener:提供定位信息发生改变时的回调功能。必须事先在定位管理器中注册监听器对象。

·Criteria:该类使得应用能够通过在LocationProvider中设置的属性来选择合适的定位提供者。

·Geocoder:用于处理地理编码和反向地理编码的类。地理编码是指将地址或其他描述转变为经度和纬度,反向地理编码则是将经度和纬度转变为地址或描述语言,其中包含了两个构造函数,需要传入经度和纬度的坐标。getFromLocation方法可以得到一组关于地址的数组。

要使用地理定位,首先需要取得LocationManager的实例,在Android中,获得LocationManager的唯一方法是通过getSystemService()方法的调用。通过使用LocationManager,我们可以获得一个位置提供者的列表。在一个真实的手持设备中,这个列表包含了一些GPS服务。我们也可以选择更强大、更精确、不带有其他附加服务的GPS。代码如下:

LocationManager?locationManager?=?(LocationManager)?getSystemService(Context.LOC-

ATION_SERVICE);?

取得LocationManager对象之后,我们还需要注册一个周期性的更新视图,代码如下:

locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,1000, 0,

locationListener);

其中第一个参数是设置服务提供者,第二个参数是周期,这里需要重点说明一下最后一个参数locationListener,它用来监听定位信息的改变,所以我们必须实现以下几个方法:

·onLocationChanged(Location location):当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发。

·onProviderDisabled(String provider):Provider禁用时触发此函数,比如GPS被关闭。

·onProviderEnabled(String provider):Provider启用时触发此函数,比如GPS被打开。

·onStatusChanged(String provider, int status, Bundle extras):Provider的转态在可用、暂时不可用和无服务三个状态直接切换时触发此函数。

下面我们通过更改上一节的例子(本书所附代码:第9章\Examples_09_04)来实现自动通过定位系统获取用户当前的坐标,然后加载并显示地图,将坐标信息显示在一个TextView中,运行效果如图9-15所示。

图9-15 地图定位

要使用定位的API,首先需要在AndroidManifest.xml文件中添加其权限,具体代码如代码清单9-5所示。

代码清单9-5 第9章\Examples_09_04\AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.yarin.android.Examples_09_04"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<uses-library android:name="com.google.android.maps" />

<activity android:name=".Activity01"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

<uses-permission android:name="android.permission.INTERNET"/>

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<uses-sdk android:minSdkVersion="5" />

</manifest>

由于我们在模拟器上测试,所以需要人为设置一个坐标。可以通过两种方法来设置一个模拟的坐标值。第一种方法是通过DDMS,我们可以在Eclipse的ADT插件中使用这种方法,只要启动Eclipse,选择“Window”->“Show View”,打开“Emulator Control”界面即可看到如下的设置窗口,我们可以手动或者通过KML和GPX文件来设置一个坐标。如图9-16所示。

图9-16 设置显示的坐标

另一种方法是使用geo命令,我们需要telnet到本机的5554端口,然后在命令行下输入类似于geo fix-121.45356 46.51119 4392 这样的命令,后面3个参数分别代表了经度、纬度和(可选的)海拔。设置之后在Android模拟器屏幕上便多出一个如图9-17所示的标志,表示模拟了GPS权限。

图9-17 GPS使用标志

现在我们可以使用位置管理器(LocationManager)和位置提供者进行getFromLocation的调用。这个方法返回本机当前位置的一个快照,这个快照将以 Location对象形式提供。在手持设备中,我们可以获得当前位置的经度和纬度;调用getFromLocationName方法可能返回一个数据,表示一个地方的名称。该例中我们还创建了一个菜单用来缩放地图,这时就使用地图控制器(MapController)的zoomIn和zoomOut方法来放大和缩小视图,具体实现如代码清单9-6所示。

代码清单9-6 第9章\Examples_09_04\src\com\yarin\android\Examples_09_04\Activity01.java

public class Activity01 extends MapActivity

{

public MapController mapController;

public MyLocationOverlay myPosition;

public MapView myMapView;

private static final int ZOOM_IN=Menu.FIRST;

private static final int ZOOM_OUT=Menu.FIRST+1;

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

//取得LocationManager实例

LocationManager locationManager;

String context=Context.LOCATION_SERVICE;

locationManager=(LocationManager)getSystemService(context);

myMapView=(MapView)findViewById(R.id.MapView01);

//取得MapController实例,控制地图

mapController=myMapView.getController();

//设置显示模式

myMapView.setSatellite(true);

myMapView.setStreetView(true);

//设置缩放控制,这里我们自己实现缩放菜单

myMapView.displayZoomControls(false);

//设置使用MyLocationOverlay来绘图

mapController.setZoom(17);

myPosition=new MyLocationOverlay();

List<Overlay> overlays=myMapView.getOverlays();

overlays.add(myPosition);

//设置Criteria(服务商)的信息

Criteria criteria =new Criteria();

//经度要求

criteria.setAccuracy(Criteria.ACCURACY_FINE);

criteria.setAltitudeRequired(false);

criteria.setBearingRequired(false);

criteria.setCostAllowed(false);

criteria.setPowerRequirement(Criteria.POWER_LOW);

//取得效果最好的criteria

String provider=locationManager.getBestProvider(criteria, true);

//得到坐标相关的信息

Location location=locationManager.getLastKnownLocation(provider);

//更新坐标

updateWithNewLocation(location);

//注册一个周期性的更新,3000ms更新一次

//locationListener用来监听定位信息的改变

locationManager.requestLocationUpdates(provider, 3000, 0,locationListener);

}

private void updateWithNewLocation(Location location)

{

String latLongString;

TextView myLocationText = (TextView)findViewById(R.id.TextView01);

String addressString="没有找到地址\n";

if(location!=null)

{

//为绘制标志的类设置坐标

myPosition.setLocation(location);

//取得经度和纬度

Double geoLat=location.getLatitude()*1E6;

Double geoLng=location.getLongitude()*1E6;

//将其转换为int型

GeoPoint point=new GeoPoint(geoLat.intValue(),geoLng.intValue());

//定位到指定坐标

mapController.animateTo(point);

double lat=location.getLatitude();

double lng=location.getLongitude();

latLongString="经度:"+lat+"\n纬度:"+lng;

double latitude=location.getLatitude();

double longitude=location.getLongitude();

//根据地理环境来确定编码

Geocoder gc=new Geocoder(this,Locale.getDefault());

try

{

//取得地址相关的一些信息、经度、纬度

List<Address> addresses=gc.getFromLocation(latitude, longitude,1);

StringBuilder sb=new StringBuilder();

if(addresses.size()>0)

{

Address address=addresses.get(0);

for(int i=0;i<address.getMaxAddressLineIndex();i++)

sb.append(address.getAddressLine(i)).append("\n");

sb.append(address.getLocality()).append("\n");

sb.append(address.getPostalCode()).append("\n");

sb.append(address.getCountryName());

addressString=sb.toString();

}

}catch(IOException e){}

}

else

{

latLongString="没有找到坐标.\n";

}

//显示

myLocationText.setText("你当前的坐标如下:\n"+latLongString+"\n"+addressString);

}

private final LocationListener locationListener=new LocationListener()

{

//当坐标改变时触发此函数

public void onLocationChanged(Location location)

{

updateWithNewLocation(location);

}

//Provider禁用时触发此函数,比如GPS被关闭

public void onProviderDisabled(String provider)

{

updateWithNewLocation(null);

}

//Provider启用时触发此函数,比如GPS被打开

public void onProviderEnabled(String provider){}

//Provider的转态在可用、暂时不可用和无服务三个状态直接切换时触发此函数

public void onStatusChanged(String provider,int status,Bundle extras){}

};

protected boolean isRouteDisplayed()

{

return false;

}

//为应用程序添加菜单

public boolean onCreateOptionsMenu(Menu menu)

{

super.onCreateOptionsMenu(menu);

menu.add(0, ZOOM_IN, Menu.NONE, "放大");

menu.add(0, ZOOM_OUT, Menu.NONE, "缩小");

return true;

}

public boolean onOptionsItemSelected(MenuItem item)

{

super.onOptionsItemSelected(item);

switch (item.getItemId())

{

case (ZOOM_IN):

//放大

mapController.zoomIn();

return true;

case (ZOOM_OUT):

//缩小

mapController.zoomOut();

return true;

}

return true;

}

class MyLocationOverlay extends Overlay

{

Location mLocation;

//在更新坐标时,设置该坐标,以便画图

public void setLocation(Location location)

{

mLocation = location;

}

@Override

public boolean draw(Canvas canvas,MapView mapView,boolean shadow,long when)

{

super.draw(canvas, mapView, shadow);

Paint paint = new Paint();

Point myScreenCoords = new Point();

// 将经纬度转换成实际屏幕坐标

GeoPoint tmpGeoPoint = new GeoPoint((int)(mLocation.

getLatitude()*1E6),(int)(mLocation.getLongitude()*1E6));

mapView.getProjection().toPixels(tmpGeoPoint,myScreenCoords);

paint.setStrokeWidth(1);

paint.setARGB(255, 255, 0, 0);

paint.setStyle(Paint.Style.STROKE);

Bitmap bmp = BitmapFactory.decodeResource(getResources(),

R.drawable.home);

canvas.drawBitmap(bmp,myScreenCoords.x,myScreenCoords.y,paint);

canvas.drawText("Here am I",myScreenCoords.x,myScreenCoords.

y, paint);

return true;

}

}

}

9.4 桌面组件

第一次启动Android模拟器时,可以看到在桌面上有很多图标,如图9-18所示的Google搜索框、时钟、联系人、浏览器等,点击这些图标,系统就会执行相应的程序,与PC操作系统桌面上的快捷方式很像,但是它不完全是快捷方式,还包括了实时文件夹(Live Folder)和桌面插件(Widget),这样既美观又方便用户操作。本节将学习这每一种桌面组件的开发,让我们自己的应用程序也能轻松地放置到桌面上。

图9-18 Android桌面组件

9.4.1 快捷方式

首先我们学习最基本的桌面组件快捷方式,它和PC上的快捷方式一样,用于启动某一应用程序的某个组件(如Activity、Service等)。其实要在桌面上添加一个快捷方式很简单,只需要长按桌面或者点击“Menu”按键(如图9-19所示),就可以弹出添加桌面组件的选项,如图9-20所示,“Shortcuts”为添加快捷方式,“Widgets”为Widget开发的桌面插件,“Folders”为实时文件夹,进入相应的选项后即可添加相应的桌面组件。

图9-19 Menu菜单 图9-20 添加桌面组件

本小节重点介绍在应用程序中通过代码来将一个应用程序添加到图9-20的Shortcuts列表中,这里添加一个发送邮件的应用到快捷方式列表上去(参见本书所附代码:第9章\Examples_09_05)。

首先需要在Activity注册时添加一个Action为android.intent.action.CREATE_SHORTCUT的IntentFilter,如代码清单9-7所示,添加之后列表中就会出现该应用的图标和名字了。

代码清单9-7 第9章\Examples_09_05\AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.yarin.android.Examples_09_05"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name=".Activity01"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

<action android:name="android.intent.action.CREATE_SHORTCUT"/>

</intent-filter>

</activity>

</application>

<uses-sdk android:minSdkVersion="5" />

</manifest>

接下来还要为快捷方式设置名字、图标、事件等属性。Intent.EXTRA_SHORTCUT_NAME对应快捷方式的名字;Intent.EXTRA_SHORTCUT_ICON_RESOURCE对应快捷方式的图标;Intent. EXTRA_SHORTCUT_INTENT对应快捷方式执行的事件。需要说明的是,Android专门提供了Intent.ShortcutIconResource.fromContext来创建快捷方式的图标,最后通过setResult来返回,构建一个快捷方式,如代码清单9-8所示。

代码清单9-8 第9章\Examples_09_05\src\com\yarin\android\Examples_09_05\Activity01.java

public class Activity01 extends Activity

{

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

//要添加的快捷方式的Intent

Intent addShortcut;

//判断是否要添加快捷方式

if (getIntent().getAction().equals(Intent.ACTION_CREATE_SHORTCUT))

{

addShortcut = new Intent();

//设置快捷方式的名字

addShortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME,"发送邮件");

//构建快捷方式中专门的图标

Parcelable icon = Intent.ShortcutIconResource.fromContext

(this,R.drawable.mail_edit);

//添加快捷方式图标

addShortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,icon);

//构建快捷方式执行的Intent

Intent mailto=new Intent(Intent.ACTION_SENDTO, Uri.parse

("mailto:xxx@xxx.com" ));

//添加快捷方式Intent

addShortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT,mailto);

//正常

setResult(RESULT_OK,addShortcut);

}

else

{

//取消

setResult(RESULT_CANCELED);

}

//关闭

finish();

}

}

现在我们启动模拟器,就可以在Shortcuts列表中找到所添加的快捷方式,将其添加到桌面,如图9-21所示。

图9-21 桌面快捷方式

9.4.2 实时文件夹

在Android 1.5中,Live Folders无疑是一个备受关注的新功能。简单地说,Live Folders就是一个查看你的手机中所有电子书、电子邮件、rss订阅、播放列表的快捷方式,并且这些内容是实时更新的。比如你不再需要单独打开电子邮件软件查看邮件,打开通讯录找联系人等。Live Folders自带了列出所有联系人、所有有电话号码的联系人以及Starred联系人的功能,我们还可以使用Live Folders API开发出更多的新颖应用。

由于Live Folders本身不存储任何信息,都是以映射的方式查看其ContentProvider所指向的数据信息,并可以自定义显示格式,所以当源数据发生改变后,Live Folders可以实时更新显示内容。那么在开发时,我们要确保所指定数据信息URI的ContentProvider支持实时文件夹的查询。

其添加方式和添加快捷方式一样,只是在选择时要选择“Folders”。本小节我们通过Live Folders调用电话本中的信息,当点击其中一条信息时,便执行呼叫该联系人的动作(本书所附代码:第9章\ Examples_09_06)。

和创建快捷方式一样,我们需要在Activity注册时添加一个Action动作为android.intent.action. CREATE_LIVE_FOLDER的IntentFilter。代码如下:

<intent-filter>

<action android:name= "android.intent.action.CREATE_LIVE_FOLDER" />

<category android:name= "android.intent.category.DEFAULT" />

</intent-filter>

我们需要在程序中设置该实时文件夹的数据源、图标、名字的信息。可以通过intent.setData方法来设置要读取的数据源,该例中我们设置数据源为“content://contacts/live_folders/people”,即联系人信息。其他信息的设置如表9-2所示。

表9-2 Live Folders的常用属性

在设置图标时,Android专门提供了Intent.ShortcutIconResource.fromContext来设置实时文件夹的图标。下面我们将实时文件夹添加到桌面(如图9-22所示),运行效果如图9-23所示。

图9-22 “电话本”实时文件夹

图9-23 实时文件夹运行效果

下面需要在onCreate方法中将实时文件夹的相关信息装入Intent对象,并通过setResult方法设置为结果Intent,最后调用finish方法结束Activity,把结果返回给Home应用程序,以添加实时文件夹,如代码清单9-9所示。

代码清单9-9 第9章\Examples_09_06\src\com\yarin\android\Examples_09_06\Activity01.java

public class Activity01 extends Activity

{

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

// setContentView(R.layout.main);

// 判断是否创建实时文件夹

if (getIntent().getAction().equals(LiveFolders.ACTION_CREATE_LIVE_FOLDER))

{

Intent intent = new Intent();

// 设置数据地址

intent.setData(Uri.parse("content://contacts/live_folders/

people"));

// 设置单击之后的事件,这里单击一个联系人后,呼叫

intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT,

new Intent(Intent.ACTION_CALL,Contacts.People.CONTENT_URI));

// 设置实时文件夹的名字

intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME,"电话本");

// 设置实施文件夹的图标

intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON, Intent.

ShortcutIconResource.fromContext(this,R.drawable.contacts));

// 设置显示模式为列表

intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE,

LiveFolders.DISPLAY_MODE_LIST);

// 完成

setResult(RESULT_OK, intent);

}

else

{

setResult(RESULT_CANCELED);

}

finish();

}

}

9.4.3 Widget开发

Widget是一种很小的应用程序,主要作为Web 2.0服务或互联网内容的前端。Web设计人员与开发者可以使用Widget来创造最受欢迎的互联网体验。在Android 1.5中加入了AppWidget framework框架,开发者可以使用该框架开发Widget,这些Widget可以拖到用户的桌面并且可以交互。Widget可以提供一个full-featured apps的预览,例如可以显示即将到来的日历事件,或者一首后台播放的歌曲的详细信息。当Widget被拖到桌面上时,指定一个保留的空间来显示应用提供的自定义内容。用户可以通过这个Widget来和应用交互,例如暂停或切换歌曲。如果你有一个后台服务,可以按照你自己的Schedule更新你的Widget,或者使用AppWidget framework提供一个自动的更新机制。

每个Widget就是一个BroadcastReceiver,它们用XML metadata来描述Widget的细节。AppWidget framework通过Broadcast intents和Widget通信, Widget的更新使用RemoteViews来发送。RemoteViews被包装成一个layout和特定内容来显示到桌面上。下面我们通过一个示例来学习Widget开发(本书所附代码:第9章\Examples_09_07)。

首先需要在res\layout目录下创建桌面组件的布局文件appwidget_provider.xml,用来显示桌面布局,这里我们创建一个TextView用来显示一段文字,如代码清单9-10所示。

代码清单9-10 第9章\Examples_09_07\res\layout\appwidget_provider.xml

<?xml version="1.0" encoding="utf-8"?>

<TextView xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/appwidget_text"

android:textColor="#ff000000"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

/>

然后需要创建一个描述这个桌面组件属性的文件,存放到res\xml文件夹下,如代码清单9-11所示。

代码清单9-11 第9章\Examples_09_07\res\xmlappwidget_provider.xml

<?xml version="1.0" encoding="utf-8"?>

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

android:minWidth="100dp"

android:minHeight="50dp"

android:updatePeriodMillis="86400000"

android:initialLayout="@layout/appwidget_provider"

android:configure="com.yarin.android.Examples_09_07.Activity01"

>

</appwidget-provider>

其中android:minWidth和android:minHeight分别指定了桌面组件的最小宽度和最小高度,其值可以根据期望的单元格数量并使用前面介绍过的公式来计算(最小尺寸=(单元格数×74)?2),android:updatePeriodMillis是自动更新的时间间隔,android:initialLayout是Widget的界面描述文件。Android:configure是可选的,如果你的Widget需要在启动前先启动一个Activity,则需要设定该项为你的Activity。这里我们需要先输入一段文字,然后显示在Widget上。

然后要建立一个Widget,创建一个类,让其继承类AppWidgetProvider。在AppWidgetProvider中有许多方法,包括onUpdate(周期更新时调用)、onDeleted(删除组件时调用)、onEnabled(当第一个组件创建时调用)、onDisabled(当最后一个组件删除时调用),如代码清单9-12所示。

代码清单9-12 第9章\Examples_09_07\src\com\yarin\android\Examples_09_07\ExampleAppWidget- Provider.java

public class ExampleAppWidgetProvider extends AppWidgetProvider

{

//周期更新时调用

public void onUpdate(Context context,AppWidgetManager appWidgetManager,int[]

appWidgetIds)

{

final int N = appWidgetIds.length;

for (int i = 0; i < N; i++)

{

int appWidgetId = appWidgetIds[i];

String titlePrefix=Activity01.loadTitlePref(context,appWidgetId);

updateAppWidget(context, appWidgetManager, appWidgetId,

titlePrefix);

}

}

//当桌面组件删除时调用

public void onDeleted(Context context, int[] appWidgetIds)

{

//删除appWidget

final int N = appWidgetIds.length;

for (int i = 0; i < N; i++)

{

Activity01.deleteTitlePref(context, appWidgetIds[i]);

}

}

//当AppWidgetProvider提供的第一个组件创建时调用

public void onEnabled(Context context)

{

PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(new ComponentName("com.yarin.android.

Examples_09_07", ".ExampleBroadcastReceiver"),

PackageManager.COMPONENT_ENABLED_STATE_ENABLED,

PackageManager.DONT_KILL_APP);

}

//当AppWidgetProvider提供的最后一个组件删除时调用

public void onDisabled(Context context)

{

PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(new ComponentName("com.yarin.

android.Examples_09_07", ".ExampleBroadcastReceiver"),

PackageManager.COMPONENT_ENABLED_STATE_ENABLED,

PackageManager.DONT_KILL_APP);

}

//更新

static void updateAppWidget(Context context, AppWidgetManager

appWidgetManager, int appWidgetId, String titlePrefix)

{

//构建RemoteViews对象来对桌面组件进行更新

RemoteViews views = new RemoteViews(context.getPackageName(),

R.layout.appwidget_provider);

//更新文本内容,指定布局的组件

views.setTextViewText(R.id.appwidget_text, titlePrefix);

//将RemoteViews的更新传入AppWidget进行更新

appWidgetManager.updateAppWidget(appWidgetId, views);

}

}

其中,在updateAppWidget方法中我们构建了一个RemoteViews对象来对桌面组件进行更新,通过setTextViewText方法来更新一个文本的显示,然后通过updateAppWidget方法来将更新提供给AppWidget使其更新到桌面。在onDisabled和onEnabled方法中我们用ComponentName来表示应用程序中某个组件的完整名字。

最后,创建一个BroadcastReceiver类来接收更新的信息,在收到更新的信息之后就更新这个桌面Widget组件,如代码清单9-13所示。

代码清单9-13 第9章\Examples_09_07\src\com\yarin\android\Examples_09_07\ExampleBroadcast- Receiver.java

public class ExampleBroadcastReceiver extends BroadcastReceiver

{

public void onReceive(Context context, Intent intent)

{

//通过BroadcastReceiver来更新AppWidget

String action = intent.getAction();

if (action.equals(Intent.ACTION_TIMEZONE_CHANGED) || action.equals

(Intent.ACTION_TIME_CHANGED))

{

AppWidgetManager gm = AppWidgetManager.getInstance(context);

ArrayList<Integer> appWidgetIds = new ArrayList<Integer>();

ArrayList<String> texts = new ArrayList<String>();

Activity01.loadAllTitlePrefs(context, appWidgetIds, texts);

//更新所有AppWidget

final int N = appWidgetIds.size();

for (int i = 0; i < N; i++)

{

ExampleAppWidgetProvider.updateAppWidget(context,

gm, appWidgetIds.get(i), texts.get(i));

}

}

}

}

接下来,处理Android:configure指定的类,用来输入信息,在该类中我们监听这个按钮,当点击按钮之后,创建一个AppWidgetManager实例,然后调用ExampleAppWidgetProvider.updateAppWidget方法来更新这个Widget,通过以下代码可以取得一个AppWidgetManager实例:

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

注意,还需要在AndroidManifest.xml中注册AppWidget、BroadcastReceiver和用来输入信息的Activity,如代码清单9-14所示。

代码清单9-14 第9章\Examples_09_07\AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.yarin.android.Examples_09_07"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<receiver android:name=".ExampleAppWidgetProvider">

<meta-data android:name="android.appwidget.provider"

android:resource="@xml/appwidget_provider" />

<intent-filter>

<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

</intent-filter>

</receiver>

<activity android:name=".Activity01">

<intent-filter>

<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />

</intent-filter>

</activity>

<receiver android:name=".ExampleBroadcastReceiver" android:enabled="false">

<intent-filter>

<action android:name="android.intent.ACTION_TIMEZONE_CHANGED" />

<action android:name="android.intent.ACTION_TIME" />

</intent-filter>

</receiver>

</application>

<uses-sdk android:minSdkVersion="5" />

</manifest>

下面将该Widget添加到桌面上,和添加快捷方式一样,如图9-24所示,然后输入要显示的文字,如图9-25所示,点击“确定”按钮之后,桌面即显示我们输入的信息,如图9-26所示。

分享到:
评论

相关推荐

    android开发揭秘PDF

    第9章 Android特色开发 9.1 传感器 9.2 语音识别 9.3 GoogleMap 9.3.1 GoogleMap概述 9.3.2 准备工作 9.3.3 GoogleMapAPI的使用 9.3.4 定位系统 9.4 桌面组件 9.4.1 快捷方式 9.4.2 实时文件夹 9.4.3 Widget开发 9.5...

    《Android应用开发揭秘》附带光盘代码.

     第9章 Android特色开发  9.1 传感器  9.2 语音识别  9.3 GoogleMap  9.3.1 GoogleMap概述  9.3.2 准备工作  9.3.3 GoogleMapAPI的使用  9.3.4 定位系统  9.4 桌面组件  9.4.1 快捷方式  9.4.2 实时...

    《Android应用开发揭秘》源码

     第9章 Android特色开发  9.1 传感器  9.2 语音识别  9.3 GoogleMap  9.3.1 GoogleMap概述  9.3.2 准备工作  9.3.3 GoogleMapAPI的使用  9.3.4 定位系统  9.4 桌面组件  9.4.1 快捷方式  9.4.2 实时...

    Android应用开发揭秘pdf高清版

    第9章 Android特色开发 9.1 传感器 9.2 语音识别 9.3 GoogleMap 9.3.1 GoogleMap概述 9.3.2 准备工作 9.3.3 GoogleMapAPI的使用 9.3.4 定位系统 9.4 桌面组件 9.4.1 快捷方式 9.4.2 实时文件夹 9.4.3 Widget开发 9.5...

    《Google Android开发入门与实战》.pdf

    第13章 android综合案例二——基于google map开发个人移动地图 221 13.1 项目ui规划 221 13.2 数据存储实现 222 13.2.1 设计数据库及表结构 222 13.2.2 设计sharepreference 存储 223 13.3 项目实现...

    android开发入门与实战(下)

    第13章 Android综合案例二——基于GoogleMap开发个人移动地图 13.1 项目UI规划 13.2 数据存储实现 13.2.1 设计数据库及表结构 13.2.2 设计SharePreference存储 13.3 项目实现流程 13.3.1 创建项目工程 13.3.2 项目各...

    Google.Android开发入门与实战

    第13章 Android综合案例二——基于GoogleMap开发个人移动地图 13.1 项目UI规划 13.2 数据存储实现 13.2.1 设计数据库及表结构 13.2.2 设计SharePreference存储 13.3 项目实现流程 13.3.1 创建项目工程 13.3.2 项目各...

    android开发入门与实战(上)

    第13章 Android综合案例二——基于GoogleMap开发个人移动地图 13.1 项目UI规划 13.2 数据存储实现 13.2.1 设计数据库及表结构 13.2.2 设计SharePreference存储 13.3 项目实现流程 13.3.1 创建项目工程 13.3.2 项目各...

    Google Android开发入门与实战的代码

    第13章 Android综合案例二——基于Google Map开发个人移动地图 221 13.1 项目UI规划 221 13.2 数据存储实现 222 13.2.1 设计数据库及表结构 222 13.2.2 设计SharePreference 存储 223 13.3 项目实现...

Global site tag (gtag.js) - Google Analytics