【问题标题】:Android Map GroundOverlay doesn't display the imageAndroid Map GroundOverlay 不显示图像
【发布时间】:2020-01-23 07:20:44
【问题描述】:

Google Map GroundOverlay 不显示叠加图像。

没有错误或任何错误。 (至少不在日志中。)

代码是从 Google IO 2019 应用复制的。

地图片段文件

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        mapView.getMapAsync {
            it.mapType = GoogleMap.MAP_TYPE_NORMAL
            it.addGroundOverlay(
                GroundOverlayOptions()
                    .image(BitmapDescriptorFactory.fromResource(R.drawable.kirirom_map_overlay))
                    .positionFromBounds(viewModel.resortLocationBounds)
            )

        }
    }

MapViewModel 文件

val resortLocationBounds: LatLngBounds = LatLngBounds(BuildConfig.MAP_VIEWPORT_BOUND_SW, BuildConfig.MAP_VIEWPORT_BOUND_NE)

参考资料:

完整的 MapFragment 文件:

package kh.edu.kit.chain.app.vkclub.features.maps

import android.Manifest
import android.app.Dialog
import android.content.pm.PackageManager
import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.addCallback
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePaddingRelative
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.MapView
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.GroundOverlayOptions
import com.google.android.gms.maps.model.LatLng
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.maps.android.data.geojson.GeoJsonLayer
import kh.edu.kit.chain.app.vkclub.R
import kh.edu.kit.chain.app.vkclub.databinding.MapsFragmentBinding
import kh.edu.kit.chain.app.vkclub.functions.doOnApplyWindowInsets
import kh.edu.kit.chain.app.vkclub.shared.BottomSheetBehavior
import kh.edu.kit.chain.app.vkclub.utils.getDrawableResourceForIcon
import org.koin.android.viewmodel.ext.android.viewModel


class MapsFragment : Fragment() {

    companion object {
        const val MIN_TIME: Long = 400
        const val MIN_DISTANCE: Float = 1000F
        private const val MAPVIEW_BUNDLE_KEY = "MapViewBundleKey"
        private const val REQUEST_LOCATION_PERMISSION = 1
        private const val FRAGMENT_MY_LOCATION_RATIONALE = "my_location_rationale"
        // Threshold for when the marker description reaches maximum alpha. Should be a value
        // between 0 and 1, inclusive, coinciding with a point between the bottom sheet's
        // collapsed (0) and expanded (1) states.
        private const val ALPHA_TRANSITION_END = 0.5f
        // Threshold for when the marker description reaches minimum alpha. Should be a value
        // between 0 and 1, inclusive, coinciding with a point between the bottom sheet's
        // collapsed (0) and expanded (1) states.
        private const val ALPHA_TRANSITION_START = 0.1f
    }

    private val viewModel: MapsViewModel by viewModel()
    private var mapViewBundle: Bundle? = null
    private lateinit var mapView: MapView

    private lateinit var binding: MapsFragmentBinding
    private lateinit var bottomSheetBehavior: BottomSheetBehavior<*>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState != null) {
            mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY)
        }

        requireActivity().onBackPressedDispatcher.addCallback(this) {
            onBackPressed()
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = MapsFragmentBinding.inflate(inflater, container, false).apply {
            lifecycleOwner = viewLifecycleOwner
            viewModel = this@MapsFragment.viewModel
        }

        mapView = binding.map.apply {
            onCreate(mapViewBundle)
        }

        if (savedInstanceState == null) {
            viewModel.setMapVariant(MapVariant.DAY)
        }

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomSheet)
        val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback {
            override fun onStateChanged(bottomSheet: View, newState: Int) {
                val rotation = when (newState) {
                    BottomSheetBehavior.STATE_EXPANDED -> 0f
                    BottomSheetBehavior.STATE_COLLAPSED -> 180f
                    BottomSheetBehavior.STATE_HIDDEN -> 180f
                    else -> return
                }
                binding.expandIcon.animate().rotationX(rotation).start()
            }

            override fun onSlide(bottomSheet: View, slideOffset: Float) {

            }
        }

        bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback)

        binding.bottomSheet.post {
            val state = bottomSheetBehavior.state
            val slideOffset = when (state) {
                BottomSheetBehavior.STATE_EXPANDED -> 1f
                BottomSheetBehavior.STATE_COLLAPSED -> 0f
                else -> -1f // BottomSheetBehavior.STATE_HIDDEN
            }
            bottomSheetCallback.onStateChanged(binding.bottomSheet, state)
            bottomSheetCallback.onSlide(binding.bottomSheet, slideOffset)
        }

        val originalPeekHeight = bottomSheetBehavior.peekHeight
        binding.root.doOnApplyWindowInsets { _, insets, _ ->
            binding.map.getMapAsync {
                it.setPadding(0, 0, 0, insets.systemWindowInsetBottom)
            }

            binding.descriptionScrollview.updatePaddingRelative(bottom = insets.systemWindowInsetBottom)

            val gestureInsets = insets.systemGestureInsets
            bottomSheetBehavior.peekHeight = gestureInsets.bottom + originalPeekHeight
        }

        binding.clickable.setOnClickListener {
            if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
            } else {
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
            }
        }

        binding.descriptionScrollview.setOnScrollChangeListener { v: NestedScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int ->
            binding.sheetHeaderShadow.isActivated = v.canScrollVertically(-1)
        }

        mapView.getMapAsync { googleMap ->
            googleMap.apply {
                setOnMapClickListener { viewModel.dismissFeatureDetails() }
                setOnCameraMoveListener {
                    viewModel.onZoomChanged(googleMap.cameraPosition.zoom)
                }
                enableMyLocation(false)
            }
        }

        viewModel.mapVariant.observe(viewLifecycleOwner, Observer {
            mapView.getMapAsync { googleMap ->
                googleMap.clear()
                viewModel.loadMapFeatures(googleMap)
            }
        })

        viewModel.geoJsonLayer.observe(viewLifecycleOwner, Observer {
            updateMarkers(it ?: return@Observer)
        })

        viewModel.selectedMarkerInfo.observe(viewLifecycleOwner, Observer {
            updateInfoSheet(it ?: return@Observer)
        })

    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        mapView.getMapAsync {
            it.mapType = GoogleMap.MAP_TYPE_NORMAL
            it.addGroundOverlay(
                GroundOverlayOptions()
                    .image(BitmapDescriptorFactory.fromResource(R.drawable.kirirom_map_overlay))
                    .positionFromBounds(viewModel.resortLocationBounds)
            )
        }
    }

    private fun updateInfoSheet(markerInfo: MarkerInfo) {
        val iconRes = getDrawableResourceForIcon(binding.markerIcon.context, markerInfo.iconName)
        binding.markerIcon.apply {
            setImageResource(iconRes)
            visibility = if (iconRes == 0) View.GONE else View.VISIBLE
        }

        binding.markerTitle.text = markerInfo.title
        binding.markerSubtitle.apply {
            text = markerInfo.subtitle
            isVisible = !markerInfo.subtitle.isNullOrEmpty()
        }

        val description = Html.fromHtml(markerInfo.description ?: "")
        val hasDescription = description.isNotEmpty()
        binding.markerDescription.apply {
            text = description
            isVisible = hasDescription
        }

        binding.expandIcon.isVisible = hasDescription
        binding.clickable.isVisible = hasDescription
    }

    private fun onBackPressed(): Boolean {
        if (::bottomSheetBehavior.isInitialized &&
            bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED
        ) {
            bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
            return true
        }
        return false
    }

    private fun updateMarkers(geoJsonLayer: GeoJsonLayer) {
        geoJsonLayer.addLayerToMap()
        geoJsonLayer.setOnFeatureClickListener { feature ->
            viewModel.requestHighlightFeature(feature.id.split(",")[0])
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        val mapViewBundle = outState.getBundle(MAPVIEW_BUNDLE_KEY)
            ?: Bundle().apply { putBundle(MAPVIEW_BUNDLE_KEY, this) }
        mapView.onSaveInstanceState(mapViewBundle)
    }

    override fun onDestroy() {
        super.onDestroy()
        mapView.onDestroy()
        viewModel.onMapDestroyed()
    }

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

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

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

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

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

    private fun requestLocationPermission() {
        val context = context ?: return
        if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
            MyLocationRationaleFragment()
                .show(childFragmentManager, FRAGMENT_MY_LOCATION_RATIONALE)
            return
        }
        requestPermissions(
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            REQUEST_LOCATION_PERMISSION
        )
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_LOCATION_PERMISSION) {
            if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                enableMyLocation()
            } else {
                MyLocationRationaleFragment()
                    .show(childFragmentManager, FRAGMENT_MY_LOCATION_RATIONALE)
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }

    private fun enableMyLocation(requestPermission: Boolean = false) {
        val context = context ?: return
        when {
            ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
                    PackageManager.PERMISSION_GRANTED -> {
                mapView.getMapAsync {
                    it.isMyLocationEnabled = true
                }
                viewModel.optIntoMyLocation()
            }
            requestPermission -> requestLocationPermission()
            else -> viewModel.optIntoMyLocation(false)
        }
    }

    class MyLocationRationaleFragment : DialogFragment() {
        override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
            return MaterialAlertDialogBuilder(context)
                .setMessage(R.string.my_location_rationale)
                .setPositiveButton(android.R.string.ok) { _, _ ->
                    parentFragment!!.requestPermissions(
                        arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                        REQUEST_LOCATION_PERMISSION
                    )
                }
                .setNegativeButton(android.R.string.cancel, null) // Give up
                .create()
        }
    }
}

完整的 MapViewModel 文件:

package kh.edu.kit.chain.app.vkclub.features.maps

import androidx.lifecycle.*
import com.google.android.gms.maps.CameraUpdate
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.maps.android.data.geojson.GeoJsonFeature
import com.google.maps.android.data.geojson.GeoJsonLayer
import com.google.maps.android.data.geojson.GeoJsonPoint
import kh.edu.kit.chain.app.vkclub.BuildConfig
import kh.edu.kit.chain.app.vkclub.R
import kh.edu.kit.chain.app.vkclub.domain.map.GeoJsonData
import kh.edu.kit.chain.app.vkclub.domain.map.LoadGeoJsonFeaturesUseCase
import kh.edu.kit.chain.app.vkclub.domain.map.LoadGeoJsonParams
import kh.edu.kit.chain.app.vkclub.functions.Event
import kh.edu.kit.chain.app.vkclub.functions.Result

class MapsViewModel(
    private val loadGeoJsonFeaturesUseCase: LoadGeoJsonFeaturesUseCase
) : ViewModel() {

    val resortLocationBounds: LatLngBounds = LatLngBounds(BuildConfig.MAP_VIEWPORT_BOUND_SW, BuildConfig.MAP_VIEWPORT_BOUND_NE)

    val groundOverlayData = Pair(resortLocationBounds, R.drawable.kirirom_map_overlay)

    private val _mapVariant = MutableLiveData<MapVariant>()
    val mapVariant = Transformations.distinctUntilChanged(_mapVariant)

    private val _mapCenterEvent = MutableLiveData<Event<CameraUpdate>>()
    val mapCenterEvent: LiveData<Event<CameraUpdate>>
        get() = _mapCenterEvent

    private val loadGeoJsonResult = MutableLiveData<Result<GeoJsonData>>()

    private val _geoJsonLayer = MediatorLiveData<GeoJsonLayer>()
    val geoJsonLayer: LiveData<GeoJsonLayer>
        get() = _geoJsonLayer

    private val featureLookup: MutableMap<String, GeoJsonFeature> = mutableMapOf()
    private var hasLoadedFeature = false
    private var requestedFeatureId: String? = null

    private val focusZoomLevel = BuildConfig.MAP_CAMERA_FOCUS_ZOOM
    private var currentZoomLevel = 16

    private val _bottomSheetStateEvent = MediatorLiveData<Event<Int>>()
    val bottomSheetStateEvent: LiveData<Event<Int>>
        get() = _bottomSheetStateEvent
    private val _selectedMarkerInfo = MutableLiveData<MarkerInfo>()
    val selectedMarkerInfo: LiveData<MarkerInfo>
        get() = _selectedMarkerInfo


    init {
        _geoJsonLayer.addSource(loadGeoJsonResult) { result ->
            if (result is Result.Success) {
                hasLoadedFeature = true
                setMapFeatures(result.data.featureMap)
                _geoJsonLayer.value = result.data.geoJsonLayer
            }
        }

        _bottomSheetStateEvent.addSource(mapVariant) {
            dismissFeatureDetails()
        }
    }

    fun setMapVariant(variant: MapVariant) {
        _mapVariant.value = variant
    }

    fun onMapDestroyed() {
        hasLoadedFeature = false
        featureLookup.clear()
        _geoJsonLayer.value = null
    }

    fun loadMapFeatures(googleMap: GoogleMap) {
        val variant = _mapVariant.value ?: return

        loadGeoJsonFeaturesUseCase(
            LoadGeoJsonParams(googleMap, variant.markersResId),
            loadGeoJsonResult
        )
    }

    private fun setMapFeatures(features: Map<String, GeoJsonFeature>) {
        featureLookup.clear()
        featureLookup.putAll(features)
        updateFeaturesVisibility(currentZoomLevel.toFloat())

        val featureId = requestedFeatureId ?: return
        requestedFeatureId = null
        highlightFeature(featureId)
    }

    fun onZoomChanged(zoom: Float) {
        val zoomInt = zoom.toInt()
        if (currentZoomLevel != zoomInt) {
            currentZoomLevel = zoomInt
            updateFeaturesVisibility(zoom)
        }
    }

    private fun updateFeaturesVisibility(zoom: Float) {
        val selectedId = selectedMarkerInfo.value?.id
        featureLookup.values.forEach { feature ->
            if (feature.id != selectedId) {
                val minZoom = feature.getProperty("minZoom")?.toFloatOrNull() ?: 0f
                feature.pointStyle.isVisible = zoom >= minZoom
            }
        }
    }

    fun requestHighlightFeature(featureId: String) {
        if (hasLoadedFeature) {
            highlightFeature(featureId)
        } else {
            requestedFeatureId = featureId
        }
    }

    private fun highlightFeature(featureId: String) {
        val feature = featureLookup[featureId] ?: return
        val geometry = feature.geometry as? GeoJsonPoint ?: return
        val update = CameraUpdateFactory.newLatLngZoom(geometry.coordinates, focusZoomLevel)
        _mapCenterEvent.value = Event(update)

        val title = feature.getProperty("title")
        _selectedMarkerInfo.value = MarkerInfo(
            featureId,
            title,
            feature.getProperty("subtitle"),
            feature.getProperty("description"),
            feature.getProperty("icon")
        )

        _bottomSheetStateEvent.value = Event(BottomSheetBehavior.STATE_COLLAPSED)

    }

    fun dismissFeatureDetails() {
        _bottomSheetStateEvent.value = Event(BottomSheetBehavior.STATE_HIDDEN)
        _selectedMarkerInfo.value = null
    }

    fun optIntoMyLocation(optIn: Boolean = true) {

    }
}

data class MarkerInfo(
    val id: String,
    val title: String,
    val subtitle: String?,
    val description: String?,
    val iconName: String?
)

【问题讨论】:

    标签: android google-maps kotlin


    【解决方案1】:

    原来叠加成功了,但是变种后就清除了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多