国内的地图 SDK 目前暂不支持 Compose,之前给高德地图封装过一次,可以达到想要的效果。
这次准备封装一个通用的地图组件,把不同地图 SDK 之间共同的组件以及功能抽象出来,开发者不再需要关心不同地图 SDK 对同一功能的不同实现,真正实现在功能相同的情况下在不同地图之间无缝切换。
这次选用的地图 SDK 是高德地图和百度地图,下面是必要依赖:
// 高德地图 (已聚合导航及定位服务)
implementation("com.amap.api:navi-3dmap-location-search:10.0.800_3dmap10.0.800_loc6.4.5_sea9.7.2")
// 百度地图基础
implementation("com.baidu.lbsyun:BaiduMapSDK_Map:7.6.3")
// 百度地图定位
implementation("com.baidu.lbsyun:BaiduMapSDK_Location:9.6.4")
结构设计
通用对象
不同的地图 SDK 虽然提供的功能实现方式不同,但是它们都需要用到一些与 SDK 无关的数据类,例如经纬度、地图相机参数、POI、地图标记等,我们可以先定义这些通用的类,以便接下来的封装。
实对象容器(ActualVariable)
有时候需要用到一个地址不变但其内部属性值可以改变的对象,先封装一个对象容器:
data class ActualVariable<T>(var value: T)
fun <T> T.toActualVariable() = ActualVariable(this)
模糊对象容器(AmbiguousVariable)
不同地图之间的一些对象虽然有共同属性,但仍然有一些不同。例如百度的 POI 类提供了高德没有的信息,为了保证框架对不同 SDK 通用的前提下,给开发者提供尽可能完整的信息,于是引入一个模糊对象容器,给开发者提供完整的数据对象类。
data class AmbiguousVariable(
val clazz: KClass<*>,
var value: Any? = null
) {
override fun toString(): String {
return "{\"@class\": \"${this::class.qualifiedName}\", \"clazz\": \"${clazz.qualifiedName}\", \"value\": \"$value\"}"
}
}
经纬度(LatLng)
@Parcelize
data class LatLngPosition @JvmOverloads constructor(
var lat: Double = 0.0,
var lng: Double = 0.0
) : Parcelable {
fun toDisplayString() = "$lat, $lng"
fun isZero() = this.lat == 0.0 && this.lng == 0.0
fun compareTo(position: LatLngPosition): Boolean {
return position.lat == this.lat && position.lng == this.lng
}
override fun toString(): String {
return "{\"@class\": \"${this::class.qualifiedName}\", \"lat\": $lat, \"lng\": $lng}"
}
}
相机参数(CameraState)
data class GenericMapCameraState(
var center: LatLngPosition,
var zoom: Float,
var routation: Float,
var overLooking: Float
)
兴趣点(POI)
data class GenericPOI(
val originalObject: AmbiguousVariable,
val id: AmbiguousVariable,
val name: String,
val position: LatLngPosition
) {
override fun toString(): String {
return "{\"@class\": \"${this::class.qualifiedName}\", \"originalObject\": $originalObject, \"id\": $id, \"name\": \"$name\", \"position\": $position}"
}
}
这里的 originalObject 用来保存不同 SDK 返回的最原始的 POI 对象,考虑到 POI 的 id 的数据类型可能不一致,所以也使用了 AmbiguousVariable 来保存,但高德和百度的 poiId实际上都是 String 类型的。
地图组件
地图组件指的是地图上的指南针、大小缩放按钮、定位按钮等可以分别设置是否显示的组件,对于不同的地图 SDK 来说,这些组件的显示/隐藏方式有些不同。、
组件枚举
首先定义一个 ComponentType 枚举来表示不同组件,这个枚举应该包含所有可能的地图组件:
enum class Type {
// 指南针
COMPASS,
// 定位按钮
LOCATION_BUTTON,
// 比例尺组件
SCALE_CONTROL_BUTTON,
// 缩放按钮
ZOOM_CONTROL_BUTTON,
// 室内地图
IN_DOOR_MAP
}
组件类
接着定义一个地图组件类来保存组件的启用状态:
data class GenericMapComponent(
val type: Type,
private var enabled: Boolean = false
) {
val isEnabled: Boolean get() = enabled
fun setEnabled(enabled: Boolean) {
this.enabled = enabled
}
enum class Type {
COMPASS,
LOCATION_BUTTON,
SCALE_CONTROL,
ZOOM_CONTROL_BUTTON,
IN_DOOR_MAP
}
}
组件操作接口
现在还差一个接口以供开发者设置组件的启用状态,于是创建一个 GenericMapComponentSupport 接口
interface GenericMapComponentSupport {
/**
* This variable should be initialized by the reified MapView Class
*/
val mapComponents: Set<GenericMapComponent>
/**
* Enable/Disable map component without checking
*
* @param componentType [GenericMapComponent.Type]
* @throws Exception Unexpected exception thrown by the reified mapView class
*/
fun forceSetComponentEnabled(componentType: GenericMapComponent.Type, enabled: Boolean)
}
这个接口很简单,有一个 mapComponents 常量,因为不同 SDK 支持的地图组件是确定的,所以这个常量一旦被确定之后就无法修改,同时 GenericMapComponent 也可以保存地图组件的启用状态。
现在最基本的接口已经定义好了,接下来用上面提到的特性,完善一下这个接口:
interface GenericMapComponentSupport {
/**
* This variable should be initialized by the reified MapView Class
*/
val mapComponents: Set<GenericMapComponent>
/**
* Enable/Disable map component
*
* @param componentType [GenericMapComponent.Type]
* @throws MapComponentNotSupportedException
*/
fun setComponentEnabled(componentType: GenericMapComponent.Type, enabled: Boolean) {
val actualComponent = mapComponents.find { it.type == componentType }
if (actualComponent == null) {
throw MapComponentNotSupportedException(componentType, mapComponents.map { it.type })
}
this.forceSetComponentEnabled(componentType, enabled)
// Update states of components
actualComponent.setEnabled(enabled)
}
/**
* Enable/Disable map component without checking
*
* @param componentType [GenericMapComponent.Type]
* @throws Exception Unexpected exception thrown by the reified mapView class
*/
fun forceSetComponentEnabled(componentType: GenericMapComponent.Type, enabled: Boolean)
/**
* Enable map component
*
* @param componentType [GenericMapComponent.Type]
* @throws MapComponentNotSupportedException
*/
fun enableComponent(componentType: GenericMapComponent.Type) {
this.setComponentEnabled(componentType, true)
}
/**
* Disable map component
*
* @param componentType [GenericMapComponent.Type]
* @throws MapComponentNotSupportedException
*/
fun disableComponent(componentType: GenericMapComponent.Type) {
this.setComponentEnabled(componentType, false)
}
}
现在开发者可以通过 setComponentEnabled() 方法来安全地设置组件状态,若该地图 SDK 不支持某个组件,则抛出 MapComponentNotSupportedException 异常。
关于 MapComponentNotSupportedException 的定义如下:
class MapComponentNotSupportedException(
componentType: GenericMapComponent.Type,
supportedComponentTypes: Collection<GenericMapComponent.Type>
) : RuntimeException("This component [$componentType] is not supported by the map, allowing: $supportedComponentTypes")
地图手势
同样地,地图手势也能作为地图的一部分,单独设置是否启用。
手势类
和上面的地图组件一样,定义一个地图手势类:
data class GenericMapGesture(
val type: Type,
private var enabled: Boolean = false
) {
val isEnabled: Boolean get() = enabled
fun setEnabled(enabled: Boolean) {
this.enabled = enabled
}
enum class Type {
// 滑动
SCROLL,
// 缩放
ZOOM,
// 俯视 (倾斜)
OVER_LOOKING,
// 旋转
ROTATE,
// 双击地图是否按中心点放大
ENLARGE_CENTER_WITH_DOUBLE_CLICK
}
}
手势操作接口
这部分和上面的 地图组件操作接口 是类似的,只是接口的参数类型不同。
当开发者调用 setGestureEnabled 设置地图不支持的手势时抛出 MapGestureNotSupportedException 异常,该异常的具体定义在 GenericMapGestureSupport 接口代码的下方。
interface GenericMapGestureSupport {
/**
* This variable should be initialized by the reified MapView Class
*/
val mapGestures: Set<GenericMapGesture>
/**
* Enable/Disable map gesture
*
* @param gestureType [GenericMapGesture.Type]
* @throws MapGestureNotSupportedException
*/
fun setGestureEnabled(gestureType: GenericMapGesture.Type, enabled: Boolean) {
val actualGesture = mapGestures.find { it.type == gestureType }
if (actualGesture == null) {
throw MapGestureNotSupportedException(gestureType, mapGestures.map { it.type })
}
this.forceGestureEnabled(gestureType, enabled)
// Update states of gestures
actualGesture.setEnabled(enabled)
}
/**
* Enable/Disable map gesture without checking
*
* @param gestureType [GenericMapGesture.Type]
* @throws Exception Unexpected exception thrown by the reified mapView class
*/
fun forceGestureEnabled(gestureType: GenericMapGesture.Type, enabled: Boolean)
/**
* Enable map gesture
*
* @param gestureType [GenericMapGesture.Type]
* @throws MapGestureNotSupportedException
*/
fun enableGesture(gestureType: GenericMapGesture.Type) {
this.setGestureEnabled(gestureType, true)
}
/**
* Disable map gesture
*
* @param gestureType [GenericMapGesture.Type]
* @throws MapGestureNotSupportedException
*/
fun disableGesture(gestureType: GenericMapGesture.Type) {
this.setGestureEnabled(gestureType, false)
}
}
MapGestureNotSupportedException 的定义如下:
class MapGestureNotSupportedException(
gestureType: GenericMapGesture.Type,
supportedGestureTypes: Collection<GenericMapGesture.Type>
) : RuntimeException("This gesture [$gestureType] is not supported by the map, allowing: $supportedGestureTypes")
地图图层
地图 SDK 一般会提供普通地图、卫星图等地图图层,作为通用的地图属性之一,这一部分也必须抽象出来。
先定义一个图层枚举:
/**
* Type/Map AMap BaiduMap
* Normal Y Y
* Satellite Y Y
* Night Y N
*/
enum class LayerType {
// 普通地图
NORMAL,
// 卫星地图
SATELLITE,
// 夜景地图
NIGHT
}
和上面的地图/手势组件接口一样,定义一个 GenericMapLayerSupport 接口:
interface GenericMapLayerSupport {
val supportedMapLayers: Set<LayerType>
val currentMapLayer: ActualVariable<LayerType>
/**
* Change displaying layer of the map
*
* @param newLayer LayerType
* @throws MapLayerNotSupportedException
*/
fun changeLayer(newLayer: LayerType) {
if (!supportedMapLayers.contains(newLayer)) {
throw MapLayerNotSupportedException(newLayer, supportedMapLayers)
}
this.forceChangeLayer(newLayer)
// Update current layer
currentMapLayer.value = newLayer
}
/**
* Change displaying layer of the map without checking
*
* @param newLayer LayerType
* @throws Exception Unexpected exception thrown by the reified mapView class
*/
fun forceChangeLayer(newLayer: LayerType)
/**
* Type/Map AMap BaiduMap
* Normal Y Y
* Satellite Y Y
* Night Y N
*/
enum class LayerType {
NORMAL,
SATELLITE,
NIGHT
}
}
MapLayerNotSupportedException 异常的定义如下:
class MapLayerNotSupportedException(
layerType: GenericMapLayerSupport.LayerType,
supportedLayerTypes: Collection<GenericMapLayerSupport.LayerType>
) : RuntimeException("This layer [${layerType.name}] is not supported by the map, allowing: $supportedLayerTypes")
MapView 生命周期
地图组件一般会提供和 Activity 类似的生命周期函数,在官方给出的实例中一般是在 Activity 对应的生命周期中调用这些函数,但在 Compose 中已经把 UI 组件化,于是便与 Activity 失去了必然联系。
但是我们仍可以通过 Compose 提供的 LocalLifecycleOwner 来得到 Composable 组件的生命周期,由此来等效传统的 Activity 的生命周期。
这一部分的具体实现在下一小节的 GenericMap 中,现在先定义一个 IGenericMapLifeCycle 接口:
interface IGenericMapLifeCycle<V: View> {
/**
* On mapView component created
*
* @param mapView Reified MapView Instance
* @param context Component Context
* @param bundle Bundle
*/
fun onCreate(mapView: V, context: Context, bundle: Bundle)
/**
* On mapView component resumed
*
* @param mapView Reified MapView Instance
*/
fun onResume(mapView: V)
/**
* On mapView component paused
*
* @param mapView Reified MapView Instance
*/
fun onPause(mapView: V)
/**
* On mapView component destroyed
*
* @param mapView Reified MapView Instance
*/
fun onDestroy(mapView: V)
/**
* On mapView component in low memory
*
* @param mapView Reified MapView Instance
*/
fun onLowMemory(mapView: V)
}
泛型 V 表示的是具体的 MapView 类,百度和高德 SDK 中都提供了他们各自的 MapView。
GenericMap
接下来是最重要的 GenericMap 抽象类,对于不同的地图,都应该有一个类来实现这个抽象类,继而框架可以适配其原生的 MapView。
基本实现
先定义一个 GenericMap:
abstract class GenericMap<V: View> : IGenericMapLifecycle<V> {
}
刚才已经定义过 IGenericMapLifecycle<V> 接口了,这个生命周期接口应该由具体的地图类来实现,在 GenericMap 中统一管理地图组件的生命周期即可。
下面定义两个拓展函数,给 View 创建 lifecycleObserver 和 componentCallbacks:
/**
* Build LifecycleObserver for Composable View
*
* @param context Context
* @return LifecycleObserver
*/
private fun V.lifecycleObserver(context: Context): LifecycleObserver {
return LifecycleEventObserver { _, event ->
when(event) {
Lifecycle.Event.ON_CREATE -> {
onCreate(this@lifecycleObserver, context, Bundle())
}
Lifecycle.Event.ON_RESUME -> {
onResume(this@lifecycleObserver)
}
Lifecycle.Event.ON_PAUSE -> {
onPause(this@lifecycleObserver)
}
Lifecycle.Event.ON_DESTROY -> {
onDestroy(this@lifecycleObserver)
}
else -> {}
}
}
}
/**
* Build component callbacks
*
* @return ComponentCallbacks
*/
private fun V.componentCallbacks(): ComponentCallbacks {
return object : ComponentCallbacks {
override fun onConfigurationChanged(newConfig: Configuration) {}
override fun onLowMemory() {
onLowMemory(this@componentCallbacks)
}
}
}
接着定义一个 Composable 组件来将自定义的生命周期观察者(LifecycleObserver)应用到地图组件:
@Composable
private fun MapViewLifecycle(mapView: V) {
val context = LocalContext.current
val lifecycle = LocalLifecycleOwner.current.lifecycle
DisposableEffect(context, lifecycle, mapView) {
val mapLifecycleObserver = mapView.lifecycleObserver(context)
val callbacks = mapView.componentCallbacks()
lifecycle.addObserver(mapLifecycleObserver)
context.registerComponentCallbacks(callbacks)
onDispose {
lifecycle.removeObserver(mapLifecycleObserver)
context.unregisterComponentCallbacks(callbacks)
}
}
}
由于不同的 SDK 创建地图组件(MapView)的方法有些不同,百度和高德都要求在使用之前调用一些方法才可以使用(如同意隐私协议、全局初始化),下面给 GenericMap 定义两个抽象方法:
abstract class GenericMap<V: View> : IGenericMapLifecycle<V> {
/**
* Run before the creation of mapView
*
* @param context Component context
*/
protected abstract fun doBefore(context: Context)
/**
* Create the actual MapView instance
*
* @param context Component context
* @return Actual MapView Instance
*/
protected abstract fun createView(context: Context): V
}
于是再利用 Compose 提供的 AndroidView 就能实现 Compose 中使用原生的 View 组件了:
@Composable
fun View(
modifier: Modifier = Modifier
) {
val context = LocalContext.current
doBefore(context)
val mapView = remember {
createView(context)
}
this.mapViewInstance = mapView
AndroidView(modifier = modifier, factory = { mapView })
MapViewLifecycle(mapView = mapView)
}
现在完整的 GenericMap 如下所示:
abstract class GenericMap<V: View> : IGenericMapLifecycle<V> {
/**
* Actual mapView instance
*/
private var mapViewInstance: V? = null
/**
* Get reified mapView instance
*
* @return V (Reified MapInstance)
*/
fun getMapViewInstance(): V = this.mapViewInstance ?: throw IllegalStateException("mapViewInstance is not initialized, make sure you have called @Composable[GenericMap\$View()] before this method.")
@Composable
fun View(
modifier: Modifier = Modifier
) {
val context = LocalContext.current
doBefore(context)
val mapView = remember {
createView(context)
}
this.mapViewInstance = mapView
AndroidView(modifier = modifier, factory = { mapView })
MapViewLifecycle(mapView = mapView)
}
/**
* Run before the creation of mapView
*
* @param context Component context
*/
protected abstract fun doBefore(context: Context)
/**
* Create the actual MapView instance
*
* @param context Component context
* @return Actual MapView Instance
*/
protected abstract fun createView(context: Context): V
}
完善实现
上面还定义了 GenericMapLayerSupport、GenericMapComponentSupport 以及 GenericMapGestureExtendedSupport 接口。
虽然 GenericMap 这个抽象类不需要实现这些接口,但上面定义的地图组件和地图手势类是有默认状态的,所以地图初始化的时候应该应用地图的默认设置,也就是读取 mapComponents、mapGestures 等常量读取地图组件的默认状态,在地图创建时应用这些状态。
修改后的 View 如下所示:
// 用一个地址不变的对象容器来保存当前显示的地图图层
private val _currentMapLayer = defaultMapLayer.toActualVariable()
override val currentMapLayer: ActualVariable<GenericMapLayerSupport.LayerType>
get() = _currentMapLayer
@Composable
fun View(
modifier: Modifier = Modifier
) {
val context = LocalContext.current
doBefore(context)
val mapView = remember {
createView(context)
}
this.mapViewInstance = mapView
// Initialize all components, gestures
this.mapComponents.forEach {
this.setComponentEnabled(it.type, it.isEnabled)
}
this.mapGestures.forEach {
this.setGestureEnabled(it.type, it.isEnabled)
}
// Initialize default map layer
if (this.currentMapLayer.value != GenericMapLayerSupport.LayerType.NORMAL) {
this.changeLayer(this.currentMapLayer.value)
}
AndroidView(modifier = modifier, factory = { mapView })
MapViewLifecycle(mapView = mapView)
}
高德地图
上面写了这么多,终于到地图组件实现的部分了,下面先完成最基本的实现,将地图展示出来看看。
AMapView
按照上面的思路,创建一个 AMapView 类继承 GenericMap,并且指定高德地图支持的地图组件、手势以及图层:
class AMapView(
override val mapComponents: Set<GenericMapComponent> = setOf(
GenericMapComponent(GenericMapComponent.Type.COMPASS, false),
GenericMapComponent(GenericMapComponent.Type.LOCATION_BUTTON, true),
GenericMapComponent(GenericMapComponent.Type.ZOOM_CONTROL_BUTTON, false),
GenericMapComponent(GenericMapComponent.Type.SCALE_CONTROL, false)
),
override val mapGestures: Set<GenericMapGesture> = setOf(
GenericMapGesture(GenericMapGesture.Type.SCROLL, true),
GenericMapGesture(GenericMapGesture.Type.ZOOM, true),
GenericMapGesture(GenericMapGesture.Type.ROTATE),
GenericMapGesture(GenericMapGesture.Type.OVER_LOOKING),
GenericMapGesture(GenericMapGesture.Type.ENLARGE_CENTER_WITH_DOUBLE_CLICK),
),
override val supportedMapLayers: Set<GenericMapLayerSupport.LayerType> = setOf(
GenericMapLayerSupport.LayerType.NORMAL,
GenericMapLayerSupport.LayerType.SATELLITE,
GenericMapLayerSupport.LayerType.NIGHT
),
defaultMapLayer: GenericMapLayerSupport.LayerType = GenericMapLayerSupport.LayerType.NORMAL
) : GenericMap<MapView>(defaultMapLayer) {}
生命周期
先实现 Lifecycle 接口的所有方法:
override fun onLowMemory(mapView: MapView) {
mapView.onLowMemory()
}
override fun onDestroy(mapView: MapView) {
mapView.map.setOnCameraChangeListener(null)
mapView.onDestroy()
}
override fun onPause(mapView: MapView) {
mapView.onPause()
}
override fun onResume(mapView: MapView) {
mapView.onResume()
}
override fun onCreate(mapView: MapView, context: Context, bundle: Bundle) {
/**
* Fix:
* java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
*
* Reason:
* If this method is called, the rendered map created in [createView] will be overwritten
*/
// mapView.onCreate(bundle)
}
高德地图重复调用 mapView 的 onCreate 方法会导致之前创建的被覆盖,因此这里不要再调用 mapView 的 onCreate,避免地图无法正常使用。
地图创建
override fun doBefore(context: Context) {
MapsInitializer.updatePrivacyShow(context, true, true)
MapsInitializer.updatePrivacyAgree(context, true)
}
override fun createView(context: Context): MapView {
return MapView(context, AMapOptions()).apply {
onCreate(null)
}
}
根据高德地图 SDK 文档给出的教程,需要先调用 MapsInitializer 内的两个方法才可以正常使用地图。
组件状态设置
override fun forceSetComponentEnabled(componentType: GenericMapComponent.Type, enabled: Boolean) {
with(super.getMapViewInstance().map.uiSettings) {
when (componentType) {
GenericMapComponent.Type.COMPASS -> {
this.isCompassEnabled = enabled
}
GenericMapComponent.Type.LOCATION_BUTTON -> {
this.isMyLocationButtonEnabled = enabled
}
GenericMapComponent.Type.SCALE_CONTROL -> {
this.isScaleControlsEnabled = enabled
}
GenericMapComponent.Type.ZOOM_CONTROL_BUTTON -> {
this.isZoomControlsEnabled = enabled
}
GenericMapComponent.Type.IN_DOOR_MAP -> {
super.getMapViewInstance().map.showIndoorMap(true)
}
}
}
}
手势状态设置
override fun forceGestureEnabled(gestureType: GenericMapGesture.Type, enabled: Boolean) {
with(super.getMapViewInstance().map.uiSettings) {
when (gestureType) {
GenericMapGesture.Type.SCROLL -> {
this.isScrollGesturesEnabled = enabled
}
GenericMapGesture.Type.ZOOM -> {
this.isZoomGesturesEnabled = enabled
}
GenericMapGesture.Type.OVER_LOOKING -> {
this.isTiltGesturesEnabled = enabled
}
GenericMapGesture.Type.ROTATE -> {
this.isRotateGesturesEnabled = enabled
}
GenericMapGesture.Type.ENLARGE_CENTER_WITH_DOUBLE_CLICK -> {
this.setZoomInByScreenCenter(enabled)
}
}
}
}
切换图层
override fun forceChangeLayer(newLayer: GenericMapLayerSupport.LayerType) {
super.getMapViewInstance().map.mapType = when (newLayer) {
GenericMapLayerSupport.LayerType.NORMAL -> {
AMap.MAP_TYPE_NORMAL
}
GenericMapLayerSupport.LayerType.SATELLITE -> {
AMap.MAP_TYPE_SATELLITE
}
GenericMapLayerSupport.LayerType.NIGHT -> {
AMap.MAP_TYPE_NIGHT
}
}
}
测试
到这里就完成对高德地图的适配了,接下来测试一下吧:
class GenericMapTestActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MetaTripTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
val amap = AMapView(defaultMapLayer = GenericMapLayerSupport.LayerType.SATELLITE)
amap.View(modifier = Modifier.weight(1f))
amap.changeLayer(GenericMapLayerSupport.LayerType.NIGHT)
amap.setRotateGestureEnabled(true)
amap.setEnlargeCenterWithDoubleClickGestureEnabled(true)
}
}
}
}
}
}
}
直接使用 AMapView 以及里面由 GenericMap 提供的 View 创建地图即可,效果如下:
百度地图
和高德地图是一样的实现方法,根据百度地图 SDK 官方文档很容易实现:
class BaiduMapView(
override val mapComponents: Set<GenericMapComponent> = setOf(
GenericMapComponent(GenericMapComponent.Type.COMPASS, false),
GenericMapComponent(GenericMapComponent.Type.LOCATION_BUTTON, true),
GenericMapComponent(GenericMapComponent.Type.ZOOM_CONTROL_BUTTON, false),
GenericMapComponent(GenericMapComponent.Type.SCALE_CONTROL, false)
),
override val mapGestures: Set<GenericMapGesture> = setOf(
GenericMapGesture(GenericMapGesture.Type.SCROLL, true),
GenericMapGesture(GenericMapGesture.Type.ZOOM, true),
GenericMapGesture(GenericMapGesture.Type.ROTATE),
GenericMapGesture(GenericMapGesture.Type.OVER_LOOKING),
GenericMapGesture(GenericMapGesture.Type.ENLARGE_CENTER_WITH_DOUBLE_CLICK),
),
override val supportedMapLayers: Set<GenericMapLayerSupport.LayerType> = setOf(
GenericMapLayerSupport.LayerType.NORMAL,
GenericMapLayerSupport.LayerType.SATELLITE
),
defaultMapLayer: GenericMapLayerSupport.LayerType = GenericMapLayerSupport.LayerType.NORMAL
) : GenericMap<MapView>(defaultMapLayer) {
override fun doBefore(context: Context) {
SDKInitializer.setAgreePrivacy(context.applicationContext, true)
SDKInitializer.initialize(context.applicationContext)
SDKInitializer.setCoordType(CoordType.GCJ02)
}
override fun createView(context: Context): MapView {
return MapView(context, BaiduMapOptions()).apply {
onCreate(context, null)
}
}
override fun onLowMemory(mapView: MapView) {
// Nothing to do
}
override fun forceSetComponentEnabled(componentType: GenericMapComponent.Type, enabled: Boolean) {
with(super.getMapViewInstance()) {
val map = this.map
val uiSettings = map.uiSettings
when (componentType) {
GenericMapComponent.Type.COMPASS -> {
uiSettings.isCompassEnabled = enabled
}
GenericMapComponent.Type.LOCATION_BUTTON -> {
map.isMyLocationEnabled = enabled
}
GenericMapComponent.Type.SCALE_CONTROL -> {
this.showScaleControl(enabled)
}
GenericMapComponent.Type.ZOOM_CONTROL_BUTTON -> {
this.showZoomControls(enabled)
}
GenericMapComponent.Type.IN_DOOR_MAP -> {
map.setIndoorEnable(enabled)
}
}
}
}
override fun forceGestureEnabled(gestureType: GenericMapGesture.Type, enabled: Boolean) {
with(super.getMapViewInstance().map.uiSettings) {
when (gestureType) {
GenericMapGesture.Type.SCROLL -> {
this.isScrollGesturesEnabled = enabled
}
GenericMapGesture.Type.ZOOM -> {
this.isZoomGesturesEnabled = enabled
}
GenericMapGesture.Type.OVER_LOOKING -> {
this.isOverlookingGesturesEnabled = enabled
}
GenericMapGesture.Type.ROTATE -> {
this.isRotateGesturesEnabled = enabled
}
GenericMapGesture.Type.ENLARGE_CENTER_WITH_DOUBLE_CLICK -> {
this.setEnlargeCenterWithDoubleClickEnable(enabled)
}
}
}
}
override fun forceChangeLayer(newLayer: GenericMapLayerSupport.LayerType) {
super.getMapViewInstance().map.mapType = when (newLayer) {
GenericMapLayerSupport.LayerType.NORMAL -> {
BaiduMap.MAP_TYPE_NORMAL
}
GenericMapLayerSupport.LayerType.SATELLITE -> {
BaiduMap.MAP_TYPE_SATELLITE
}
else -> throw RuntimeException()
}
}
override fun onDestroy(mapView: MapView) {
mapView.onDestroy()
}
override fun onPause(mapView: MapView) {
mapView.onPause()
}
override fun onResume(mapView: MapView) {
mapView.onResume()
}
override fun onCreate(mapView: MapView, context: Context, bundle: Bundle) {
mapView.onCreate(context, bundle)
}
}
截止这篇文章发布的时间,百度地图官方文档中并没有支持夜景地图,其 MapView 也没有提供 onLowMemory() 方法。
结束语
目前仅支持了地图组件、地图手势以及图层切换,按照上面的思路,你可以继续完善这套框架。