2.地图位置

2.1 问题

需要在地图上为用户显示一个或多个位置。此外,要在同一张地图上显示用户自己的位置。

2.2 解决方案

(API Level 9)
向用户显示地图的最简单方式就是用位置数据创建一个Intent并把它传递给Android系统来启动地图应用程序。另外,Google Play Services库的Google Maps v2库组件提供的Map View和MapActivity可以在应用程序中嵌入地图。

要点:
Google Maps v2是作为Google Play Services库的一部分进行分发的,它在任意平台级别都不是原生SDK的一部分。然而,目标平台为API Level 9或以后版本的应用程序以及Google Play体系内的设备都可以使用此绘画库。

1. 获取API密钥

要开始使用Maps v2,需要创建一个API项目,在该项目内启用Maps v2服务,生成API密钥并包括在应用程序代码中。如果没有API密钥,虽然也可以利用绘图类,但不会向应用程序返回任何地图图块(map tile)。请遵循如下步骤:
(1)进入https://code.google.com/apis/console/,使用你的Google账户登录以访问Google API控制台。
(2)选择Create Project选项,为你的地图建立新的项目。如果已有项目,则可以根据喜好向其中添加Maps v2服务和密钥。在此例中,选择要添加Maps v2的项目。
(3)在导航面板中,选择Services,向下滚动到Google Maps Android API v2并启用该服务。
(4)在导航面板中选择API Access,并且选择Create new Android Key选项。
(5)遵循屏幕上的说明,根据想要使用的应用程序向密钥添加密钥库签名/应用程序包对。在此例中,示例应用程序的包名是com.androidrecipes.mapper,而签名来自开发机器上的调试密钥,通常位于/.android/debug.keystore。

如果在模拟器上测试你的运行代码,模拟器必须使用目标平台为Android 4.3或以后版本的SDK构建,这些版本包含Google API,从而绘图操作可以正常运行。以前版本的SDK捆绑了Map v1 库而不是Google Play Services,因此它们不能用于测试。
如果是通过命令行创建模拟器,目标的名称就是“Google Inc:Google APIs:X”,其中的X指示API的版本。如果在IDE(例如Eclipse)中创建模拟器,目标的命名约定也是类似的:Google APIs(Google Inc.) -X,其中X指示API的版本。

2.满足清单要求

获得了有效的API密钥之后,需要在我们的AndroidManifest.xml文件包括该密钥。下面的代码块必须放在元素内:

        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="YOUR_KEY_HERE" />

此外,Maps v2有一项设备要求,即至少要具备OpenGL ES2.0。我们可以将此要求作为设备特性提出,方法是在元素内添加如下代码块,通常是放在元素的上方:

<!-- Maps v2 requires OpenGL ES 2.0 -->
    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

最后,Maps v2 需要一组权限才能与Google Play Services通信以及渲染地图图块。因此,我们必须在元素内再添加一个代码块,通常是放在元素的上方:

<!-- 显示地图所需要的权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />

将上述内容整合在一起,清单文件如下所示。

AndroidManifest.xml的部分代码

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.androidrecipes.mapper" >

<!-- 需要在地图上显示用户的位置 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <!-- 显示地图所需的权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <!-- Maps v2 requires OpenGL ES 2.0 -->
    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
       
        <!--活动、服务、提供程序等-->
        
       <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="YOUR_KEY_HERE" />
    </application>

</manifest>

手边有了API密钥和合适的测试平台之后,就可以开始正式工作了。

2.3 实现机制

要想显示地图,只需要创建一个MapView或MapFragment实例。API密钥在应用程序中全局可用,因此这些元素的任何实例都会使用该值。不需要像在Maps v1中一样将密钥添加到每个实例中。

注意:
除了上述权限之后,还必须为此例添加android.permission.ACCESS_FINE_LOCATION权限。需要此权限的唯一原因是该例会连接到LoactionManager以获得缓存的位置值。

我们将创建一个简单的应用程序,它会将用户上次最新的已知位置显示在Google地图上。
用户位置的地图

现在,让我们查看构造此视图所需的布局,参见以下代码清单。
res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="Map Of Your Location" />
    <RadioGroup
        android:id="@+id/group_maptype"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <RadioButton
            android:id="@+id/type_normal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Normal Map" />
        <RadioButton
            android:id="@+id/type_satellite"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Satellite Map" />
    </RadioGroup>
    
    <fragment
        class="com.google.android.gms.maps.SupportMapFragment" 
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

注意:
在XML布局中添加MapView或MapFragment时,必须指定包的完全限定名称,这是由于该类并没有包含在android.view或android.widget中。

在此创建了一个简单布局,其中包括一个选择器,用于切换在MapFragment实例旁边显示的地图类型。以下代码清单显示了控制地图的Activity代码。

显示缓存位置的Activity

public class BasicMapActivity extends FragmentActivity implements
        RadioGroup.OnCheckedChangeListener {
    private static final String TAG = "AndroidRecipes";

    private SupportMapFragment mMapFragment;
    private GoogleMap mMap;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //检查 play services 是否激活且为最新版本
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        switch (resultCode) {
            case ConnectionResult.SUCCESS:
                Log.d(TAG, "Google Play Services is ready to go!");
                break;
            default:
                showPlayServicesError(resultCode);
                return;
        }
        
        mMapFragment = (SupportMapFragment)
                getSupportFragmentManager().findFragmentById(R.id.map);
        mMap = mMapFragment.getMap();
        
        //快速检查用户的最新已知位置是否有效, 并且围绕该点将地图居中
        // 如果该位置无效,则使用默认位置
        LocationManager manager =
                (LocationManager)getSystemService(Context.LOCATION_SERVICE);
        Location location =
                manager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        
        LatLng mapCenter;
        if(location != null) {
            mapCenter = new LatLng(location.getLatitude(),
                    location.getLongitude());
        } else {
            //使用默认位置
            mapCenter = new LatLng(37.4218,  -122.0840);
        }
        
        //居中地图并同时缩放
        CameraUpdate newCamera = CameraUpdateFactory.newLatLngZoom(mapCenter, 13);
        mMap.moveCamera(newCamera);
        
        // 连接地图类型选择器 UI
        RadioGroup typeSelect = (RadioGroup) findViewById(R.id.group_maptype);
        typeSelect.setOnCheckedChangeListener(this);
        typeSelect.check(R.id.type_normal);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mMap != null) {
            //启用地图上用户位置显示功能
            mMap.setMyLocationEnabled(true);
        }
    }
    
    @Override
    public void onPause() {
        super.onResume();
        if (mMap != null) {
            //在不可见时禁用用户位置
            mMap.setMyLocationEnabled(false);
        }
    }

    /*
     * 当 Play Services缺失或版本不对时,
     * 客户端库将以对话框的形式帮助用户进行更新。
     */
    private void showPlayServicesError(int errorCode) {
        // 从 Google Play services 获得错误对话框
        Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
                errorCode,
                this,
                1000 /* RequestCode */);
        // 如果 Google Play services 可以提供错误对话框
        if (errorDialog != null) {
            // 为错误对话框创建新的 DialogFragment 
            SupportErrorDialogFragment errorFragment = SupportErrorDialogFragment.newInstance(errorDialog);
            // 在DialogFragment中显示错误对话框
            errorFragment.show(
                    getSupportFragmentManager(),
                    "Google Maps");
        }
    }

    /** OnCheckedChangeListener 方法 */
    
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId) {
            case R.id.type_satellite:
                //显示卫星地图
                mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
                break;
            case R.id.type_normal:
            default:
                //显示普通地图
                mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
                break;
        }
    }
}

首先执行的操作是确认将正确版本的Google Play Services安装到此设备上。在设备的用户与Google应用程序(如Google Play)交互时,Google会自动管理Google Play Services库。Google Play Services在后台自动更新,因此我们需要使用来自GooglePlayServicesUtil的方法,在运行时确认用户具有我们所需的版本。从isGooglePlayServicesAvaiable()获得的结果将告诉我们服务是否为正确的版本还是需要更新,甚至是需要完全安装。
这个Activity会获得用户最新的位置信息并将该位置设为地图的中心。关于地图的所有控制操作都是通过GoogleMap实例来完成的,而GoogleMap实例则是通过调用MapFragment.getMap()得到的。在此例中,我们使用了地图的moveCamera()方法,通过CameraUpdate对象对地图的显示做了调整。
CameraUpdate用于一次性对地图显示的一个或多个组件进行调整,例如修改缩放以及中心点。地图的缩放级别是2.0和21.0之间的离散值,其中的最小值会使整个世界地图近似为1024dp宽,而每增加一级缩放就会使显示中的世界地图的宽度翻倍。
当用户选择不同的单选按钮时,地图类型会在卫星视图和传统的地图视图之间切换,除了此例中使用的值之外,其他允许的地图类型如下:

  • Map_TYPE_HYBRID:在卫星地图上显示地图数据(例如,街道和感兴趣的点)。
    -Map_TYPE_TERRAIN :使用地形海拔轮廓线显示地图。

最后,为启用用户位置显示和控件,我们只需要对地图调用setMyLocationEnabled()。该方法将启用位置跟踪并可能开启GPS等元素,因此也应该在不再需要时禁用(当视图不可见时)。
为正确构建此应用程序,以下代码清单显示了build.gradle文件中所需的依赖关系。

build.gradle文件的部分代码

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion '26.0.2'

    defaultConfig {
        applicationId "com.androidrecipes.mapper"
       ...
    }

......
}

dependencies {
    compile 'com.android.support:support-v4:21.0.+'
    compile 'com.google.android.gms:play-services:6.1.+'
}

这是很好的起点,但或许有点令人厌烦。为引入一些更具交互性的内容,下一节将在地图上创建标识和其他标记,并且会介绍如何定制这些标记。

猜你喜欢

转载自blog.csdn.net/qq_41121204/article/details/84643307