summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DraggableMarkerActivity.kt
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DraggableMarkerActivity.kt')
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DraggableMarkerActivity.kt314
1 files changed, 314 insertions, 0 deletions
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DraggableMarkerActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DraggableMarkerActivity.kt
new file mode 100644
index 0000000000..48ebc2c154
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DraggableMarkerActivity.kt
@@ -0,0 +1,314 @@
+package com.mapbox.mapboxsdk.testapp.activity.style
+
+import android.graphics.PointF
+import android.os.Bundle
+import android.support.design.widget.Snackbar
+import android.support.v7.app.AppCompatActivity
+import android.view.MotionEvent
+import android.view.View
+import com.mapbox.android.gestures.AndroidGesturesManager
+import com.mapbox.android.gestures.MoveGestureDetector
+import com.mapbox.geojson.Feature
+import com.mapbox.geojson.FeatureCollection
+import com.mapbox.geojson.Point
+import com.mapbox.mapboxsdk.annotations.IconFactory
+import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
+import com.mapbox.mapboxsdk.geometry.LatLng
+import com.mapbox.mapboxsdk.maps.MapView
+import com.mapbox.mapboxsdk.maps.MapboxMap
+import com.mapbox.mapboxsdk.style.layers.PropertyFactory.*
+import com.mapbox.mapboxsdk.style.layers.SymbolLayer
+import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
+import com.mapbox.mapboxsdk.testapp.R
+import kotlinx.android.synthetic.main.activity_draggable_marker.*
+
+/**
+ * An Activity that showcases how to make symbols draggable.
+ */
+class DraggableMarkerActivity : AppCompatActivity() {
+ companion object {
+ private const val sourceId = "source_draggable"
+ private const val layerId = "layer_draggable"
+ private const val markerImageId = "marker_icon_draggable"
+
+ private var latestId: Long = 0
+ fun generateMarkerId(): String {
+ if (latestId == Long.MAX_VALUE) {
+ throw RuntimeException("You've added too many markers.")
+ }
+ return latestId++.toString()
+ }
+ }
+
+ private val actionBarHeight: Int by lazy {
+ supportActionBar?.height ?: 0
+ }
+
+ private lateinit var mapboxMap: MapboxMap
+ private val featureCollection = FeatureCollection.fromFeatures(mutableListOf())
+ private val source = GeoJsonSource(sourceId, featureCollection)
+ private val layer = SymbolLayer(layerId, sourceId)
+ .withProperties(
+ iconImage(markerImageId),
+ iconAllowOverlap(true),
+ iconIgnorePlacement(true))
+
+ private var draggableSymbolsManager: DraggableSymbolsManager? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_draggable_marker)
+
+ mapView.onCreate(savedInstanceState)
+ mapView.getMapAsync { mapboxMap ->
+ this.mapboxMap = mapboxMap
+
+ // Setting up markers icon, source and layer
+ mapboxMap.addImage(markerImageId, IconFactory.getInstance(this).defaultMarker().bitmap)
+ mapboxMap.addSource(source)
+ mapboxMap.addLayer(layer)
+
+ // Add initial markers
+ addMarker(LatLng(52.407210, 16.924324))
+ addMarker(LatLng(41.382679, 2.181555))
+ addMarker(LatLng(51.514886, -0.112589))
+
+ // Initial camera position
+ mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
+ LatLng(45.0, 8.0), 3.0
+ ))
+
+ mapboxMap.addOnMapClickListener {
+ // Adding a marker on map click
+ val features = mapboxMap.queryRenderedSymbols(it, layerId)
+ if (features.isEmpty()) {
+ addMarker(it)
+ } else {
+ // Displaying marker info on marker click
+ Snackbar.make(
+ mapView,
+ "Marker's position: %.4f, %.4f".format(it.latitude, it.longitude),
+ Snackbar.LENGTH_LONG)
+ .show()
+ }
+ }
+
+ draggableSymbolsManager = DraggableSymbolsManager(
+ mapView, mapboxMap, featureCollection, source, layerId, 0, actionBarHeight)
+
+ // Adding symbol drag listeners
+ draggableSymbolsManager?.addOnSymbolDragListener(object : DraggableSymbolsManager.OnSymbolDragListener {
+ override fun onSymbolDragStarted(id: String) {
+ draggedMarkerPositionTv.visibility = View.VISIBLE
+ Snackbar.make(
+ mapView,
+ "Marker drag started (%s)".format(id),
+ Snackbar.LENGTH_SHORT)
+ .show()
+ }
+
+ override fun onSymbolDrag(id: String) {
+ val point = featureCollection.features()?.find {
+ it.id() == id
+ }?.geometry() as Point
+ draggedMarkerPositionTv.text = "Dragged marker's position: %.4f, %.4f".format(point.latitude(), point.longitude())
+ }
+
+ override fun onSymbolDragFinished(id: String) {
+ draggedMarkerPositionTv.visibility = View.GONE
+ Snackbar.make(
+ mapView,
+ "Marker drag finished (%s)".format(id),
+ Snackbar.LENGTH_SHORT)
+ .show()
+ }
+ })
+ }
+ }
+
+ private fun addMarker(latLng: LatLng) {
+ featureCollection.features()?.add(
+ Feature.fromGeometry(Point.fromLngLat(latLng.longitude, latLng.latitude), null, generateMarkerId()))
+ source.setGeoJson(featureCollection)
+ }
+
+ override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+ // Dispatching parent's touch events to the manager
+ draggableSymbolsManager?.onParentTouchEvent(ev)
+ return super.dispatchTouchEvent(ev)
+ }
+
+ /**
+ * A manager, that allows dragging symbols after they are long clicked.
+ * Since this manager lives outside of the Maps SDK, we need to intercept parent's motion events
+ * and pass them with [DraggableSymbolsManager.onParentTouchEvent].
+ * If we were to try and overwrite [AppCompatActivity.onTouchEvent], those events would've been
+ * consumed by the map.
+ *
+ * We also need to setup a [DraggableSymbolsManager.androidGesturesManager],
+ * because after disabling map's gestures and starting the drag process
+ * we still need to listen for move gesture events which map won't be able to provide anymore.
+ *
+ * @param mapView the mapView
+ * @param mapboxMap the mapboxMap
+ * @param symbolsCollection the collection that contains all the symbols that we want to be draggable
+ * @param symbolsSource the source that contains the [symbolsCollection]
+ * @param symbolsLayerId the ID of the layer that the symbols are displayed on
+ * @param touchAreaShiftX X-axis padding that is applied to the parent's window motion event,
+ * as that window can be bigger than the [mapView].
+ * @param touchAreaShiftY Y-axis padding that is applied to the parent's window motion event,
+ * as that window can be bigger than the [mapView].
+ * @param touchAreaMaxX maximum value of X-axis motion event
+ * @param touchAreaMaxY maximum value of Y-axis motion event
+ */
+ class DraggableSymbolsManager(mapView: MapView, private val mapboxMap: MapboxMap,
+ private val symbolsCollection: FeatureCollection,
+ private val symbolsSource: GeoJsonSource, private val symbolsLayerId: String,
+ private val touchAreaShiftX: Int = 0, private val touchAreaShiftY: Int = 0,
+ private val touchAreaMaxX: Int = mapView.width, private val touchAreaMaxY: Int = mapView.height) {
+
+ private val androidGesturesManager: AndroidGesturesManager = AndroidGesturesManager(mapView.context, false)
+ private var draggedSymbolId: String? = null
+ private val onSymbolDragListeners: MutableList<OnSymbolDragListener> = mutableListOf<OnSymbolDragListener>()
+
+ init {
+ mapboxMap.addOnMapLongClickListener {
+ // Starting the drag process on long click
+ draggedSymbolId = mapboxMap.queryRenderedSymbols(it, symbolsLayerId).firstOrNull()?.id()?.also { id ->
+ mapboxMap.uiSettings.setAllGesturesEnabled(false)
+ mapboxMap.gesturesManager.moveGestureDetector.interrupt()
+ notifyOnSymbolDragListeners {
+ onSymbolDragStarted(id)
+ }
+ }
+ }
+
+ androidGesturesManager.setMoveGestureListener(MyMoveGestureListener())
+ }
+
+ inner class MyMoveGestureListener : MoveGestureDetector.OnMoveGestureListener {
+ override fun onMoveBegin(detector: MoveGestureDetector): Boolean {
+ return true
+ }
+
+ override fun onMove(detector: MoveGestureDetector, distanceX: Float, distanceY: Float): Boolean {
+ if (detector.pointersCount > 1) {
+ // Stopping the drag when we don't work with a simple, on-pointer move anymore
+ stopDragging()
+ return true
+ }
+
+ // Updating symbol's position
+ draggedSymbolId?.also { draggedSymbolId ->
+ val moveObject = detector.getMoveObject(0)
+ val point = PointF(moveObject.currentX - touchAreaShiftX, moveObject.currentY - touchAreaShiftY)
+
+ if (point.x < 0 || point.y < 0 || point.x > touchAreaMaxX || point.y > touchAreaMaxY) {
+ stopDragging()
+ }
+
+ val latLng = mapboxMap.projection.fromScreenLocation(point)
+
+ symbolsCollection.features()?.indexOfFirst {
+ it.id() == draggedSymbolId
+ }?.also { index ->
+ symbolsCollection.features()?.get(index)?.also { oldFeature ->
+ val properties = oldFeature.properties()
+ val newFeature = Feature.fromGeometry(
+ Point.fromLngLat(latLng.longitude, latLng.latitude),
+ properties,
+ draggedSymbolId
+ )
+ symbolsCollection.features()?.set(index, newFeature)
+ symbolsSource.setGeoJson(symbolsCollection)
+ notifyOnSymbolDragListeners {
+ onSymbolDrag(draggedSymbolId)
+ }
+ return true
+ }
+ }
+ }
+
+ return false
+ }
+
+ override fun onMoveEnd(detector: MoveGestureDetector, velocityX: Float, velocityY: Float) {
+ // Stopping the drag when move ends
+ stopDragging()
+ }
+ }
+
+ private fun stopDragging() {
+ mapboxMap.uiSettings.setAllGesturesEnabled(true)
+ draggedSymbolId?.let {
+ notifyOnSymbolDragListeners {
+ onSymbolDragFinished(it)
+ }
+ }
+ draggedSymbolId = null
+ }
+
+ fun onParentTouchEvent(ev: MotionEvent?) {
+ androidGesturesManager.onTouchEvent(ev)
+ }
+
+ private fun notifyOnSymbolDragListeners(action: OnSymbolDragListener.() -> Unit) {
+ onSymbolDragListeners.forEach(action)
+ }
+
+ fun addOnSymbolDragListener(listener: OnSymbolDragListener) {
+ onSymbolDragListeners.add(listener)
+ }
+
+ fun removeOnSymbolDragListener(listener: OnSymbolDragListener) {
+ onSymbolDragListeners.remove(listener)
+ }
+
+ interface OnSymbolDragListener {
+ fun onSymbolDragStarted(id: String)
+ fun onSymbolDrag(id: String)
+ fun onSymbolDragFinished(id: String)
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ mapView.onStart()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ mapView.onResume()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ mapView.onPause()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ mapView.onStop()
+ }
+
+ override fun onLowMemory() {
+ super.onLowMemory()
+ mapView.onLowMemory()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ mapView.onDestroy()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle?) {
+ super.onSaveInstanceState(outState)
+ outState?.let {
+ mapView.onSaveInstanceState(it)
+ }
+ }
+}
+
+private fun MapboxMap.queryRenderedSymbols(latLng: LatLng, layerId: String): List<Feature> {
+ return this.queryRenderedFeatures(this.projection.toScreenLocation(latLng), layerId)
+}