迁移到 www.trinea.cn @Android @Java @性能优化 @开源,滴滴国际化项目 Android 端演进

Android开发中调用Google Map或Baidu Map

本文主要介绍如何在Android中引入地图及并对地图进行相应的操作。包括如何申请Google Map API密钥、如何创建包含地图的项目、地图的功能使用(地图缩放、设置视图、定位到自己的位置、添加标记、获得屏幕坐标的位置、双击放大、位置搜索即经纬度位置及屏幕像素坐标和具体地址的转换、监听某个位置)、google map和百度地图API的差别
如今lbs正火热,android开发中地图的相关操作必不可少。百度地图API 和google地图类似,本文以google地图为例,下面的完整代码可以见MapDemo@Google Code。示例APK地址可见TrineaAndroidDemo@GoogleCode

1、申请Google Map API密钥
a、从debug.keystore文件中提取MD5值
进入debug.keystore文件路径下,路径为C:\用户\当前用户\.android(xp路径为C:\Documents and Settings\当前用户\.android),路径也可以在Eclipse工具下查看,选择windows-->Preference-->Android-->Build,其中Default debug keystore的值便是debug.keystore的路径了
执行命令:keytool -list -keystore debug.keystore -v,这时可能会提示你输入密码,这里默认的密码是“android",这样即可取得MD5值(16位)。

ps:以上命令必须加上-v,否则获得的是SHA1码(长度20位),在下一步申请时输入后会提示“您输入的指纹无效”

b. 申请Map API密钥
打开http://code.google.com/android/maps-api-signup.html,需要用google账号登陆,填入你的认证指纹(MD5)点击获取API key即可获得API Key。
PS:此key只能作为debug使用,若要发布应用需要重新申请key替换此key。

2、新建项目
新建Android项目,注意Build Target选择Google APIs而不是Android API。Google APIs包含了Android相应版本,同时加入了google自己的一些服务,目前只有google map。

3、编写代码
3.1 layout中添加MapView

<com.google.android.maps.MapView  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    android:apiKey="yourAPIKey"  
    /> 

修改对应的后台Activity继承自MapActivity而不是Activity,否则会提示MapView只允许在MapActivity之类中使用。


3.2 AndroidManifest.xml文件添加权限
a. 在节点application内添加<uses-library android:name="com.google.android.maps" /> 表示引入maps库,否则会提示找不到MapView类
b. 在节点manifest内添加<uses-permission android:name="android.permission.INTERNET" /> 表示允许访问网络,否则MapView会显示空白
如下:

如此,运行程序即可
下面我们介绍如何使用google map提供的一些功能

4 map功能
4.1 地图缩放
在OnCreate函数中添加

MapView gMap = (MapView) findViewById(R.id.mapview);  
gMap.setBuiltInZoomControls(true);  

gMap.displayZoomControls(true)可以显示缩放控件。

可以手动调用mapView.getController().zoomIn()或zoomOut()进行放大和缩小,或setZoom(zoomLevel)设置倍数。

 

4.2 设置视图

gMap.setStreetView(true); // 街道视图 
gMap.setSatellite(true); // 卫星视图 
gMap.setTraffic(true); // 交通状况可见 

 

4.3 定位到自己的位置
AndroidManifest文件中添加权限 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />,本文以GPS获取位置为例,其他如WIFI方式需要添加其他权限
使用LocationManager的getLastKnownLocation获得自己的位置,MapController的animateTo定位到固定位置

MapView gMap = (MapView)findViewById(R.id.googleMapView);
MapController gMapController = gMap.getController();

LocationManager lm = (LocationManager)getSystemService(LOCATION_SERVICE);
Location l = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (l == null) {
    Toast.makeText(getApplication(), "无法获取自己的位置", Toast.LENGTH_SHORT);
} else {
    GeoPoint gp = new GeoPoint((int)(l.getLatitude() * 1E6), (int)(l.getLongitude() * 1E6));
    gMapController.animateTo(gp);
    gMapController.setZoom(13);
}

也可以使用locationManager的getBestProvider获得最好的提供商再去获取自己的位置

随着移动更新GPS信息 ,定义自己的LocationListener

更新自己的位置
public class GMapLocationListener implements LocationListener {

    @Override
    public void onLocationChanged(Location location) {
        GeoPoint gp = new GeoPoint((int)(location.getLatitude() * 1E6), (int)(location.getLongitude() * 1E6));
        gMapController.animateTo(gp);
        gMapController.setZoom(13);
    }

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

    }

    @Override
    public void onProviderEnabled(String provider) {
    }

    @Override
    public void onProviderDisabled(String provider) {
    }
}

重写onLocationChanged方法在里面定位到最新的位置,下面注册位置监听器

LocationManager lm = (LocationManager)getSystemService(LOCATION_SERVICE); 
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10 * 1000, 2, new GMapLocationListener()); 

其中10*1000为收到更新通知的最小时间,以毫秒为单位,2为收到更新通知的最小距离,以米为单位。


4.4 添加标记
定义自己的覆盖层

定义自己的覆盖层
    public class GoogleMapOverlay extends Overlay {

        private GeoPoint gp;

        GoogleMapOverlay(GeoPoint gp){
            super();
            this.gp = gp;
        }

        GoogleMapOverlay(int latitudeE6, int longitudeE6){
            super();
            gp = new GeoPoint(latitudeE6, longitudeE6);
        }

        @Override
        public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) {
            super.draw(canvas, mapView, shadow, when);
            Point p = new Point();
            gMap.getProjection().toPixels(gp, p);

            Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.mark);
            canvas.drawBitmap(bmp, p.x, p.y, null);
            return true;
        }
    }

继承Overlay重写draw方法,其中的gMap.getProjection()表示得到地图经纬度坐标和手机像素坐标之间的转换对象,toPixels函数表示将经纬度坐标转换为手机像素坐标。在canvas上绘制R.drawable.mark的图像,添加自己的标记,可能需要根据图片大小自己精确调整标记位置。下面显示图像

显示标记
// 显示标记
GoogleMapOverlay overlay = new GoogleMapOverlay(gp);
List<Overlay> overlays = gMap.getOverlays();
overlays.clear();
overlays.add(overlay);
gMap.invalidate();

先获得map的overlays然后清楚添加自己的图层并刷新。

注意Overlay和ItemizedOverlay的区别,Overlay更通用,可用来标记某条路线或某个区域等面状,如某条公交路线或某个城市的区域;ItemizedOverlay是Overlay的子类,可用来标记一系列的点,如一些列餐馆。对于百度地图API还有RouteOverlay和TransitOverlay等,ItemizedOverlay定义举例如下:

ItemizedOverlay定义
public class GoogleMapItemOverlay extends ItemizedOverlay<OverlayItem> {

    public GoogleMapItemOverlay(Drawable defaultMarker){
        super(boundCenterBottom(defaultMarker));
        populate();
    }

    @Override
    protected OverlayItem createItem(int i) {
        return new OverlayItem(new GeoPoint((int)(1.3333 * 1E6), (int)(1.3333 * 1E6)), "title", "snippet");
    }

    /**
     * 返回标记的数目
     * 
     * @return
     */
    @Override
    public int size() {
        return 1;
    }
}

 

4.5 获得触摸的位置
重写上面自定义的Overlay的onTouchEvent方法,使用类Projection的fromPixels转换屏幕像素坐标和经纬度坐标

获得触摸的位置
@Override
public boolean onTouchEvent(MotionEvent e, MapView mapView) {
    if (e.getAction() == MotionEvent.ACTION_UP) {
        GeoPoint p = gMap.getProjection().fromPixels((int)e.getX(), (int)e.getY());
        Toast.makeText(getApplication(), "点击位置坐标为" + p.getLatitudeE6() / 1E6 + ", " + p.getLongitudeE6() / 1E6,
                       Toast.LENGTH_SHORT).show();
    }
    return super.onTouchEvent(e, mapView);
}

 

4.6 双击放大
其实只要判断双击即可,因为放大直接调用mapView.getController().zoonIn()函数即可

View Code
private static long DOUBLE_CLICK_INTERVAL = 300;
private long        lastTouchTime         = 0;

public class GoogleMapOverlay extends Overlay {

    @Override
    public boolean onTouchEvent(MotionEvent e, MapView mapView) {
        if (e.getAction() == MotionEvent.ACTION_UP) {
            // 判断是否是有效双击,是则放大
            long t = System.currentTimeMillis();
            if (t - lastTouchTime < DOUBLE_CLICK_INTERVAL) {
                gMapController.zoomIn();
            }
            lastTouchTime = t;
        }
        return super.onTouchEvent(e, mapView);
    }
    ……
}

以上类对于添加1个GoogleMapOverlay可行,若添加多个GoogleMapOverlay,双击会造成多个GoogleMapOverlay同时触发onTouchEvent函数造成指数级放大,因为点击区域被多个overlay响应。如下

多个图层
List<Overlay> overlays = gMap.getOverlays();
overlays.clear();
GoogleMapOverlay overlay1 = new GoogleMapOverlay(new GeoPoint((int)(1.3333 * 1E6), (int)(1.3333 * 1E6)));
overlays.add(overlay1);
GoogleMapOverlay overlay2 = new GoogleMapOverlay(new GeoPoint((int)(1.3334 * 1E6), (int)(1.3343 * 1E6)));
overlays.add(overlay2);
GoogleMapOverlay overlay3 = new GoogleMapOverlay(new GeoPoint((int)(1.3335 * 1E6), (int)(1.3353 * 1E6)));
overlays.add(overlay3);
gMap.invalidate();

若要解决可将多个待标记位置放在同一个图层中,如此修改

新定义的图层
public class GoogleMapOverlay extends Overlay {

    private List<GeoPoint> gpList = new ArrayList<GeoPoint>();

    public GoogleMapOverlay(GeoPoint gp){
        super();
        gpList.add(gp);
    }

    public GoogleMapOverlay(List<GeoPoint> gpList){
        super();
        if (gpList != null && gpList.size() > 0) {
            this.gpList.addAll(gpList);
        }
    }

    public GoogleMapOverlay(int latitudeE6, int longitudeE6){
        super();
        gpList.add(new GeoPoint(latitudeE6, longitudeE6));
    }

    @Override
    public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) {
        super.draw(canvas, mapView, shadow, when);

        Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.mark);
        for (GeoPoint gp : gpList) {
            Point p = new Point();
            gMap.getProjection().toPixels(gp, p);

            // 显示标记,可以自己精确调整显示
            canvas.drawBitmap(bmp, p.x, p.y, null);
        }
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent e, MapView mapView) {
        if (e.getAction() == MotionEvent.ACTION_UP) {

            /**
             * 判断是否是有效双击,是则放大
             */
            long t = System.currentTimeMillis();
            if (t - lastTouchTime < DOUBLE_CLICK_INTERVAL) {
                gMapController.zoomIn();
            }
            lastTouchTime = t;
        }
        return super.onTouchEvent(e, mapView);
    }
}

调用如下

View Code
List<Overlay> overlays = gMap.getOverlays();
overlays.clear();
List<GeoPoint> gpList = new ArrayList<GeoPoint>();
gpList.add(new GeoPoint((int)(1.3333 * 1E6), (int)(1.3333 * 1E6)));
gpList.add(new GeoPoint((int)(1.3334 * 1E6), (int)(1.3343 * 1E6)));
gpList.add(new GeoPoint((int)(1.3335 * 1E6), (int)(1.3353 * 1E6)));
overlays.add(new GoogleMapOverlay(gpList));
gMap.invalidate();

 

4.7 位置搜索即经纬度位置、屏幕像素坐标和具体地址的转换
已经经纬度位置或屏幕像素坐标如何得到具体的地址呢会是已知木某个地名如何得到屏幕像素让其在屏幕上显示呢,如点击了屏幕某个位置想获得他的具体名字并显示。这时可以使用Geocoder的getFromLocation或getFromLocationName进行转换

位置搜索
    /**
     * 从经纬度坐标获得地址列表
     **/
    public String getAddressFromGeoPoint(GeoPoint p) {
        StringBuilder s = new StringBuilder();
        Geocoder gc = new Geocoder(getApplication(), Locale.getDefault());
        try {
            // 根据坐标地址搜索地址
            List<Address> l = gc.getFromLocation(p.getLatitudeE6() / 1E6, p.getLongitudeE6() / 1E6, MAX_ADDR_RESULT_NUM);
            if (l != null && l.size() > 0) {
                for (Address a : l) {
                    for (int i = 0; i < a.getMaxAddressLineIndex(); i++) {
                        s.append(a.getAddressLine(i));
                    }
                    s.append("\n");
                }
            }
        } catch (IOException e) {
            Toast.makeText(getApplication(), "获取地址异常", Toast.LENGTH_SHORT).show();
        }
        return s.toString();
    }

    /**
     * 从地址获得经纬度坐标
     **/
    public List<GeoPoint> getGeoPointFromAddress(String address) {
        List<GeoPoint> gpList = new ArrayList<GeoPoint>();
        Geocoder gc = new Geocoder(getApplication(), Locale.getDefault());
        try {
            // 根据具体位置搜索地址
            List<Address> l = gc.getFromLocationName(address, MAX_ADDR_RESULT_NUM);
            if (l != null && l.size() > 0) {
                for (Address a : l) {
                    for (int i = 0; i < a.getMaxAddressLineIndex(); i++) {
                        gpList.add(new GeoPoint((int)(a.getLatitude() * 1E6), (int)(a.getLongitude() * 1E6)));
                    }
                }
            }
        } catch (IOException e) {
            Toast.makeText(getApplication(), "获取地址异常", Toast.LENGTH_SHORT).show();
        }
        return gpList;
    }

其中的MAX_ADDR_RESULT_NUM表示最多返回的结果数目,本例中为5

getAddressFromGeoPoint可以根据某个经纬度坐标找到对应的地址,可以先使用4.5 获得触摸的位置得到经纬度坐标再获取地址。
getGeoPointFromAddress可以根据某个地名得到其经纬度坐标,然后可以使用4.4中添加标记的方法进行标记,如getGeoPointFromAddress("上海图书馆").


4.8 监听某个位置
使用locationManager的addProximityAlert函数


5、google map和百度地图API的差别
在定位、缩放上API几乎没有区别但在搜索、路线、覆盖物图层方面区别巨大,百度地图开发的API较多,可以方便的搜索某个位置、某类地点、绘制公交路线、驾驶路线等等,关于如何使用可以参见百度地图API介绍,很详细,而google map暂时貌似没有开放。

 

6、解决Installation error: INSTALL_FAILED_MISSING_SHARED_LIBRARY错误,见http://www.cnblogs.com/trinea/archive/2012/11/14/2770408.html

参考:
Google地图Android平台开发指南:https://developers.google.com/maps/documentation/android/hello-mapview
百度地图Android平台开发指南:http://developer.baidu.com/map/sdkandev-1.htm

 

posted @ 2012-11-14 19:20  Trinea  阅读(10298)  评论(0编辑  收藏  举报