Android高德SDK开发简单地图

1,683 阅读5分钟

项目地址:Niko2727/Amap: A simple map demo, using Amap sdk (github.com)

1. 概述

一个简单的地图演示,使用高德地图SDK实现。目前有两个页面,定位和导航。

在定位页面,您可以在地图上查看您当前的位置,并在2D和3D视图之间切换。当您在地图上选择一个位置时,页面底部会出现三个按钮,用于步行、驾车和骑行。通过点击它们,您可以选择如何导航到您选择的位置。

location page

在导航页面,您可以跟随地图上的路线到达目的地。

location page

2. Android 高德SDK

如果您想了解高德SDK的完整内容,可以查看官方网站

2.1 获取高德key

点击链接申请高德key

  1. 点击页面左侧的"我的应用"

image.png

  1. 点击页面右侧的"创建新应用"

image.png

创建应用后,点击添加key并填写应用信息。

image.png

2.2 在主项目的build.gradle文件中配置依赖

implementation("com.amap.api:3dmap:latest.integration")

2.3 添加高德key

为了确保高德Android SDK功能的常规使用,您需要申请高德Key并将其配置到项目中。

在项目的"AndroidManifest.xml"文件中,添加以下代码:

<application
         android:icon="@drawable/icon"
         android:label="@string/app_name" >
         <meta-data
            android:name="com.amap.api.v2.apikey"
            android:value="input your user Key"/>
            ……
</application> 
           

2.4 配置权限

在AndroidManifest.xml中配置权限:

    <!--Allow access to the network, required permissions-->
    <uses-permission android:name="android.permission.INTERNET" />
    
    <!--llows obtaining rough location, required if GPS is used to locate the small blue dot function-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    
    <!--Used to access GPS positioning-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    
    <!--Allows access to network status for network positioning. If there is no GPS but the function of positioning the small blue dot still needs to be achieved, this permission is required.-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
    <!--Allows access to wifi network information for network positioning. If there is no GPS but still needs to locate the small blue dot, this permission is required.-->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    
    <!--Allows access to wifi status changes for network positioning. If there is no GPS but still needs to locate the small blue dot, this permission is required.-->
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    
    <!--Allow writing to extended storage, used for data caching. If you do not have this permission, write to a private directory.-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    <!--Allow writing to device cache for troubleshooting purposes-->
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    
    <!--Allows reading device and other information for troubleshooting-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
    <!--If target >= 28 is set, this permission must be declared if background positioning needs to be started.-->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    
    <!--If your app requires background positioning permissions, may run on Android Q devices, and is set to target>28, you must add this permission statement.-->
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

2.5 隐私合规接口

在构建MapView之前必须执行合规性检查。在设置接口之前,确保隐私政策合规。检查接口如下:

MapsInitializer.updatePrivacyShow(context,true,true);
MapsInitializer.updatePrivacyAgree(context,true);

3. 定位页面

3.1 初始化地图容器

MapView是AndroidView类的子类,用于在Android视图中放置地图。MapView是地图容器。使用MapView加载地图的方法与其他由Android提供的视图相同。具体使用步骤如下:

首先在布局xml文件中添加地图控件:

<com.amap.api.maps.MapView
    android:id="@+id/map"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

在使用项目中的地图时,需要注意合理管理地图生命周期,这非常重要。

以下示例简要概述了地图生命周期管理:

public class MainActivity extends Activity {
  MapView mMapView = null;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main);
    mMapView = (MapView) findViewById(R.id.map);
    mMapView.onCreate(savedInstanceState);
  }
  
  @Override
  protected void onDestroy() {
    super.onDestroy();
    mMapView.onDestroy();
  }
  
 @Override
 protected void onResume() {
    super.onResume();
    mMapView.onResume();
  }
  
 @Override
 protected void onPause() {
    super.onPause();
    mMapView.onPause();
  }
  
 @Override
 protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    mMapView.onSaveInstanceState(outState);
  } 
}

3.2 显示地图

AMap类是地图的控制器类,用于操作地图。它承担的任务包括:切换地图图层(如卫星图像、夜间地图)、更改地图状态(地图旋转角度、俯仰角度、中心点坐标和缩放级别)、添加点标记(Marker)、绘制几何图形(Polyline、Polygon、Circle)、各种事件监控(点击、手势等),AMap是地图SDK最重要的核心类,许多操作都依赖它来完成。

MapView对象初始化后,构建AMap对象。示例代码如下:

mapView = (MapView) findViewById(R.id.map);
mapView.onCreate(savedInstanceState);
AMap aMap;
if (aMap == null) {
    aMap = mapView.getMap();      
}

3.3 显示定位蓝点

val locationStyle = MyLocationStyle() //Initialize positioning blue dot style class
locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) // Positioning blue dot display mode

with(map) {
    myLocationStyle = locationStyle   //Set the Style for positioning the blue dot
    isMyLocationEnabled = true // Set to true to start displaying the positioning blue dot, false to hide the positioning blue dot and not perform positioning. The default is false.

    setOnMapLoadedListener {
        showMapText(true)
        showIndoorMap(true)
        showBuildings(true)
    }

    moveCamera(CameraUpdateFactory.zoomTo(defaultZoomLevel))
  }

with(map.uiSettings) {
    isMyLocationButtonEnabled = false // Sets whether to display the default positioning button. This setting is not required.
    isZoomControlsEnabled = true 
}

3.4 定位按钮

点击定位按钮,您可以切换到定位和跟随模式。

locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) // positioning mode
locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_MAP_ROTATE)      // following mode

map.myLocationStyle = locationStyle

map.moveCamera(newCameraPosition(CameraPosition(getUserLatlng(), zoomLevel, tilt, 0f))) // change zoom level and tilt

3.5 投影按钮

点击定位按钮,您可以切换到2D和3D模式。

private val tilt2D = 0f
private val tilt3D = 45f

val degree = if (it == MainViewModel.ProjectionStyle.ThreeD) tilt3D else tilt2D

map.animateCamera(CameraUpdateFactory.changeTilt(degree))

3.6 选择目的地

地图上可点击的位置以POIs的形式存在于代码中。选择目的地的代码如下:


map.addOnPOIClickListener {onClickPOI(it)}


private fun onClickPOI(poi: Poi) {
        markerOption.position(poi.coordinate)

        // display marker on selected position
        map.addMarker(markerOption).apply {
            setAnimation(markerAnimation)
            startAnimation()
        }
    }

4. 导航页面

4.1 启动导航页面

启动导航页面需要传入起点和终点位置信息、导航类型以及是否使用真实GPS信号。

fun start(context: Context, start: NaviLatLng, end: NaviLatLng, naviType: AmapNaviType, isGps: Boolean) {
            val intent = Intent(context, NaviActivity::class.java)
            intent.putExtra(EXTRA_START, start)
            intent.putExtra(EXTRA_END, end)
            intent.putExtra(EXTRA_NAVI_TYPE, naviType.name)
            intent.putExtra(EXTRA_IS_GPS, isGps)
            context.startActivity(intent)
        }

4.2 初始化地图容器

定义布局,导航需要使用AmapNaviView,而不是定位使用的MapView

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.amap.navifragement.NaviFragment">

    <com.amap.api.navi.AMapNaviView
        android:id="@+id/navi_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

实现AMapNaviView的生命周期

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mAMapNaviView.onCreate(savedInstanceState);
    mAMapNaviView.setAMapNaviViewListener(this);
}

@Override
public void onResume() {
    super.onResume();
    mAMapNaviView.onResume();
}

@Override
public void onPause() {
    super.onPause();
    mAMapNaviView.onPause();
}

@Override
public void onDestroy() {
    super.onDestroy();
    mAMapNaviView.onDestroy();
}

4.3 配置AMapNavi

AMapNavi是一个导航控制单元,可以计算两个点之间的路径,并在到达目的地时提供回调。

mapNavi = AMapNavi.getInstance(applicationContext)
mapNavi.addAMapNaviListener(naviListener)
mapNavi.setEmulatorNaviSpeed(30)
mapNavi.setUseInnerVoice(true, true)

4.4 计算路线

AMapNavi提供了一个接口,让我们计算步行、骑行和驾车路线。然而,摩托车和电动单车目前是收费的。如果未支付费用,调用时将不会返回结果。

private fun calculateRoute(start: NaviLatLng, end: NaviLatLng, naviType: AmapNaviType) {

        when (naviType) {
            AmapNaviType.WALK -> {
                mapNavi.calculateWalkRoute(start, end)
            }

            AmapNaviType.RIDE -> {
                mapNavi.calculateRideRoute(start, end)
            }

            AmapNaviType.MOTORCYCLE -> {
                mapNavi.calculateEleBikeRoute(start, end)
            }

            else -> {
                calculateDriveRoute(start, end)
            }
        }
    }

4.5 开始导航

当路径计算成功时,绘制路径并开始导航

  1. 绘制路径
private val naviListener = object : DefaultAmapNaviListener() {
override fun onCalculateRouteSuccess(p0: AMapCalcRouteResult?) {
    super.onCalculateRouteSuccess(p0)

    // 当重新规划路线时,移除之前的路线
    routeOverLay?.removeFromMap()
    routeOverLay?.destroy()

    // 获取返回的路线的数组routIDs aMapCalcRouteResult将返回一个或多个路线。
    // ps: 多条路线用于选择多条路线,但这里我们只做简单导航。所以我们只绘制一个。
    val routIds = p0!!.routeid
    val routeId = routIds[0]

    val path = mapNavi.naviPaths[routeId]
    routeOverLay = RouteOverLay(map, path, this@NaviActivity)
    routeOverLay?.addToMap()

    this@NaviActivity.onCalculateRouteSuccess()
}

override fun onLocationChange(p0: AMapNaviLocation?) {
    super.onLocationChange(p0)
    //绘制灰色行驶路线
    routeOverLay?.updatePolyline(p0)
}

}

  1. 开始导航
fun onCalculateRouteSuccess() {
        if (isGps) {
            mapNavi.startNavi(NaviType.GPS)
        } else {
            mapNavi.startNavi(NaviType.EMULATOR)
        }
    }

4.6 到达目的地

到达目的地时显示距离和时间

private val naviListener = object : DefaultAmapNaviListener() {
  override fun onArriveDestination() {
            super.onArriveDestination()
            Log.d(TAG, "onArriveDestination() called")
            vm.onNaviEnd(this@NaviActivity)
        }

        override fun onEndEmulatorNavi() {
            super.onEndEmulatorNavi()
            vm.onNaviEnd(this@NaviActivity)
        }
}

fun onNaviEnd(context: Context) {
        val realDuration = getTimeDisplayString(((System.currentTimeMillis() - naviStartTime) / 1000).toInt(), context)
        val duration = getTimeDisplayString(naviPath!!.allTime, context)
        val distanceDisplay = getDistanceDisplayString(naviPath!!.allLength, context)


        val stringBuilder = StringBuilder()
        stringBuilder.append(context.getString(R.string.distance)).append(":").append(distanceDisplay).append('\n')
        stringBuilder.append(context.getString(R.string.estimated_duration)).append(":").append(duration).append('\n')
        stringBuilder.append(context.getString(R.string.real_duration)).append(":").append(realDuration)

        _naviEndInfo.value = stringBuilder.toString()
}