首页 > 解决方案 > 将 mapbox 地图附加到 livedata

问题描述

我的目标是使用 MapBox 并将视图连接到包含 GeoJSON 多边形的数据集。我已经能够通过 LiveData<List> 从数据集中获取更改,其中 Case 其中包含 GeoJSON 区域。

现在我想从 ViewModel 监听这个数据集的变化,并将结果绑定到地图中的特定层。我找不到任何有关如何执行此操作的示例,因为大多数示例不使用数据绑定或带有 LiveData 的 ViewModel。

data class Case (
    var id : String,
    var feature : Feature,
    var note : String?
)

注意:Feature 是实现 GeoJSON 的 MapBox.Feature,它是一个多边形。

我已经能够听到案例列表上的变化,但还没有弄清楚如何连接剩余的部分。你能帮我完成这里的步骤,或者指出一个很好的例子吗?一个可能的答案可能是一些解释以及一些伪代码。

<com.mapbox.mapboxsdk.maps.MapView
            android:id="@+id/MyMapView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/jobSelectionJobTitle"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:mapbox_cameraZoom="12" />

还有一个额外的问题:我不确定在活动中投入多少以及在模型视图中投入什么。

标签: androidkotlinmapbox

解决方案


此示例使用:

  • 数据绑定
  • 绑定适配器
  • 实时数据
  • Kotlin 协程
  • com.mapbox.mapboxsdk.style.sources.GeoJsonSource
  • com.mapbox.mapboxsdk.style.layers.Layer

目的是将对象绑定到via 数据绑定GeoJsonSource和. 还提供一种通过“id”删除层的机制。我故意将 LiveData 的实现留空,因为我不知道如何提供这些数据,但提供了实现这一点的机制。此机制可根据您的需要进行调整。LayerMapViewLiveData

数据类:

data class GeoJsonLayer(val source: GeoJsonSource, val layer: Layer)

绑定适配器(放置在单独的 kotlin 文件中以供全局访问)

@BindingAdapter(value = ["addGeoJsonLayers", "addCoroutineScope"], requireAll = true)
fun MapView.addGeoJsonLayers(layers: List<GeoJsonLayer>?, scope: CoroutineScope?) {
    layers?.let { list ->
        scope?.launch {
            getStyle()?.run {
                list.filter { getSource(it.source.id) == null }
                    .forEach { jsonLayer ->
                        addSource(jsonLayer.source)
                        addLayer(jsonLayer.layer)
                    }
            }
        }
    }
}

@BindingAdapter(value = ["removeLayers", "removeCoroutineScope"], requireAll = true)
fun MapView.removeLayers(ids: List<String>?, scope: CoroutineScope?) {
    ids?.let { list ->
        scope?.launch {
            getStyle()?.run {
                list.forEach { id ->
                    removeLayer(id)
                    removeSource(id)
                }
            }
        }
    }
}

协程扩展函数(放置在单独的 kotlin 文件中以供全局访问)

suspend fun MapView.getMap(): MapboxMap = suspendCoroutine { cont -> getMapAsync { cont.resume(it) } }

suspend fun MapView.getStyle(): Style? = getMap().style

示例 ViewModel 合同

abstract class MapViewModel : ViewModel() {
    
    abstract val addGeoJsonLayers: LiveData<List<GeoJsonLayer>>

    abstract val removeGeoJsonLayers: LiveData<List<String>>
}

XML 布局( layout/map_view.xml)

<data>

    <import type="android.view.View" />

    <variable
        name="mapVm"
        type="your.package.MapViewModel" />

    <variable
        name="scope"
        type="kotlinx.coroutines.CoroutineScope" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/map_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:addCoroutineScope="@{scope}"
        app:addGeoJsonLayers="@{mapVm.addGeoJsonLayers}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:removeCoroutineScope="@{scope}"
        app:removeLayers="@{mapVm.removeGeoJsonLayers}" />

</androidx.constraintlayout.widget.ConstraintLayout>

示例活动

class MapActivity : AppCompatActivity() {

    private lateinit var binding: MapViewBinding
    private val viewModel: ConcreteMapViewModel by viewModels() // implementation of MapViewModel

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        binding = MapViewBinding.inflate(layoutInflater).apply { 
            setContentView(root)
            lifecycleOwner = lifecycleOwner
            scope = lifecycleOwner.lifecycleScope
            mapView.onCreate(savedInstanceState)
            lifecycleOwner.lifecycleScope.launch {
                mapView.getMap().setStyle(Style.MAPBOX_STREETS)
            }
            mapVm = viewModel
        }
    }

    override fun onStart() {
        super.onStart()
        binding.mapView.onStart()
    }

    override fun onResume() {
        super.onResume()
        binding.mapView.onResume()
    }

    override fun onPause() {
        super.onPause()
        binding.mapView.onPause()
    }


    override fun onStop() {
        super.onStop()
        binding.mapView.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        binding.mapView.onDestroy()
    }

    override fun onLowMemory() {
        super.onLowMemory()
        binding.mapView.onLowMemory()
    }
}

Gradle 文件设置(kts - kotlin DSL):

plugins {
    id("com.android.application")
    id("kotlin-android")
    id("kotlin-kapt")
}

android {
    compileSdk = 30
    buildToolsVersion = "30.0.3"

    defaultConfig {
        applicationId = "com.package.name"
        minSdk = 24
        targetSdk = 30
        versionCode = 1
        versionName = "1.0.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        named("release") {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    buildFeatures {
        dataBinding = true
    }
}

dependencies {

    // Kotlin
    implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.10")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0")

    // AndroidX
    val lifecycleVersion = "2.3.1"
    val navVersion = "2.3.5"
    implementation("androidx.core:core-ktx:1.5.0")
    implementation("androidx.appcompat:appcompat:1.3.0")
    implementation("androidx.constraintlayout:constraintlayout:2.0.4")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")

    // MapBox
    implementation("com.mapbox.mapboxsdk:mapbox-android-sdk:9.6.1")
    implementation("com.google.code.gson:gson:2.8.7")

    // Logging
    implementation("com.jakewharton.timber:timber:4.7.1")

    testImplementation("junit:junit:4.13.2")
}

推荐阅读