如标题所见,目前高德官方并未适配 Jetpack Compose,因此如果想要在 Compose 中使用高德地图的话,就必须自行适配原来的 xml 布局。
引入依赖
以下是高德官方给出的 gradle 依赖:
Android Studio 配置工程-创建工程-开发指南-Android 地图SDK|高德地图API (amap.com)
SDK | 引入代码 |
---|---|
3D地图 | compile ‘com.amap.api:3dmap:latest.integration’ |
2D地图 | compile ‘com.amap.api:map2d:latest.integration’ |
导航 | compile ‘com.amap.api:navi-3dmap:latest.integration’ |
搜索 | compile ‘com.amap.api:search:latest.integration’ |
定位 | compile ‘com.amap.api:location:latest.integration’ |
但是经过测试,如果你想同时使用地图和搜索,引入导航和搜索 SDK 会发生依赖冲突,寻求官方的解决办法之后,只需要引入下面的依赖即可:
implementation("com.amap.api:navi-3dmap-location-search:10.0.800_3dmap10.0.800_loc6.4.5_sea9.7.2")
应用权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
生命周期
先看看官方给出的实例:
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);
//在activity执行onCreate时执行mMapView.onCreate(savedInstanceState),创建地图
mMapView.onCreate(savedInstanceState);
}
@Override
protected void onDestroy() {
super.onDestroy();
//在activity执行onDestroy时执行mMapView.onDestroy(),销毁地图
mMapView.onDestroy();
}
@Override
protected void onResume() {
super.onResume();
//在activity执行onResume时执行mMapView.onResume (),重新绘制加载地图
mMapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
//在activity执行onPause时执行mMapView.onPause (),暂停地图的绘制
mMapView.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//在activity执行onSaveInstanceState时执行mMapView.onSaveInstanceState (outState),保存地图当前的状态
mMapView.onSaveInstanceState(outState);
}
}
观察发现,上面的 MapView 和 Activity 一样,也有对应的生命周期事件。
现在解决第一个问题,在 Compose 中监听生命周期并且调用 MapView 的相关方法。
首先由 LocalLifecycleOwner.current.lifecycle 可以得到当前的生命周期,然后添加观察者来感知生命周期的变化。
于是可以得到下面的关于 MapView 的拓展函数:
private fun MapView.lifecycleObserver(
onCreate: (() -> Unit)? = null,
onResume: (() -> Unit)? = null,
onPause: (() -> Unit)? = null,
onDestroy: (() -> Unit)? = null,
): LifecycleObserver {
return LifecycleEventObserver { _, event ->
when(event) {
Lifecycle.Event.ON_CREATE -> {
this.onCreate(Bundle())
onCreate?.invoke()
}
Lifecycle.Event.ON_RESUME -> {
this.onResume()
onResume?.invoke()
}
Lifecycle.Event.ON_PAUSE -> {
this.onPause()
onPause?.invoke()
}
Lifecycle.Event.ON_DESTROY -> {
this.map.setOnCameraChangeListener(null)
this.onDestroy()
onDestroy?.invoke()
}
else -> {}
}
}
}
private fun MapView.componentCallbacks(
onLowMemory: (() -> Unit)? = null,
): ComponentCallbacks {
return object : ComponentCallbacks {
override fun onConfigurationChanged(newConfig: Configuration) {}
override fun onLowMemory() {
this@componentCallbacks.onLowMemory()
onLowMemory?.invoke()
}
}
}
接下来可以通过 Compose 提供的 AndroidView 组件来使用原生的 View:
val context = LocalContext.current
val mapView = remember {
MapView(context, AMapOptions()).apply {
onCreate(null)
}
}
AndroidView(modifier = Modifier, factory = { mapView })
组件封装
写到这里,好像就可以用了,但是别忘了我们还没有给 MapView 绑定生命周期事件:
@Composable
private fun AMapViewLifecycle(mapView: MapView) {
val context = LocalContext.current
val lifecycle = LocalLifecycleOwner.current.lifecycle
DisposableEffect(context, lifecycle, mapView) {
val mapLifecycleObserver = mapView.lifecycleObserver()
val callbacks = mapView.componentCallbacks()
lifecycle.addObserver(mapLifecycleObserver)
context.registerComponentCallbacks(callbacks)
onDispose {
lifecycle.removeObserver(mapLifecycleObserver)
context.unregisterComponentCallbacks(callbacks)
}
}
}
然后把这个组件结合起来,就得到最终封装好的高德地图组件了:
@Composable
fun AMapView(modifier: Modifier = Modifier) {
val context = LocalContext.current
val mapView = remember {
MapView(context, AMapOptions()).apply {
onCreate(null)
}
}
AndroidView(modifier = modifier, factory = { mapView })
AMapViewLifecycle(mapView = mapView)
}
组件通信
现在还有最后一个问题,在这个组件内部怎么与外部进行交流呢?
最常用的方法应该是自定义一个状态类和 remember 函数,然后传递给这个组件,下面来实践一下吧:
状态类
class GenericMapViewState(
initialPosition: LatLngPosition = LatLngPosition(),
@FloatRange(from = 0.0, to = 1.0)
initialZoom: Float = 0.6f
) {
private val _centerPosition: MutableState<LatLngPosition> = mutableStateOf(initialPosition)
private val _zoom: MutableState<Float> = mutableFloatStateOf(initialZoom)
val centerPosition: State<LatLngPosition> get() = _centerPosition
val zoom: State<Float> get() = _zoom
fun updateCenterPosition(position: LatLngPosition) {
_centerPosition.value = position
}
fun updateZoom(zoom: Float) {
_zoom.value = zoom
}
}
remember
下面给出一种可以保存状态的方法,可以避免切换页面后地图又回到最初的状态,但需要配合组件使用:
@Composable
fun rememberSavableGenericMapViewState(
initialPosition: LatLngPosition = LatLngPosition(),
initialZoom: Float = 0.6f
): GenericMapViewState {
return rememberSaveable(
saver = Saver(
save = { it.toJSONString() },
restore = { it.toExplicitObject<String, GenericMapViewState?>() }
)
) { GenericMapViewState(
initialPosition = initialPosition,
initialZoom = initialZoom
) }
}
这里的 toJSONString() 和 toExplicitObject() 是我自己用 fastjson2 封装的函数:
fun Any?.toJSONString(): String = JSON.toJSONString(this)
inline fun <T : CharSequence, reified R> T.toExplicitObject(): R = JSON.parseObject(this.toString(), R::class.java)
组件内部监听
现在来改造一下刚才封装好的组件,首先添加一个 state 参数:
@Composable
fun AMapView(
modifier: Modifier = Modifier,
state: GenericMapViewState
) {
val context = LocalContext.current
val mapView = remember {
MapView(context, AMapOptions()).apply {
onCreate(null)
}
}
AndroidView(modifier = modifier, factory = { mapView })
AMapViewLifecycle(mapView = mapView)
}
接着在地图初始化的时候,将摄像机指向 GenericMapViewState 中保存的中心点位置:
@Composable
fun AMapView(
modifier: Modifier = Modifier,
state: GenericMapViewState
) {
val context = LocalContext.current
val mapView = remember {
MapView(context, AMapOptions()).apply {
onCreate(null)
map.apply {
// Initialize
moveCamera(CameraUpdateFactory.changeLatLng(state.centerPosition.value.fitAMap()))
moveCamera(CameraUpdateFactory.zoomTo(getRealZoom(state.zoom.value)))
}
}
}
AndroidView(modifier = modifier, factory = { mapView })
AMapViewLifecycle(mapView = mapView)
}
由于 GenericMapViewState 中保存的是 State<T> 变量,因此你也可以直接在组件内部和外部监听它们值的变化,从而实现共享状态。
总结
要在 Compose 中封装地图组件其实并不复杂,如果有任何错误,欢迎指出。