Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[android] - added DraggableMarkerActivity
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasPaczos authored and Łukasz Paczos committed Jul 6, 2018
1 parent 3bd24e6 commit 282f89a
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -345,15 +345,15 @@
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.snapshot.MapSnapshotterLocalStyleActivity"
android:description="@string/description_map_snapshotter_local_style"
android:label="@string/activity_map_snapshotter_local_style">
android:name=".activity.snapshot.MapSnapshotterLocalStyleActivity"
android:description="@string/description_map_snapshotter_local_style"
android:label="@string/activity_map_snapshotter_local_style">
<meta-data
android:name="@string/category"
android:value="@string/category_imagegenerator" />
android:name="@string/category"
android:value="@string/category_imagegenerator" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.maplayout.DoubleMapActivity"
Expand Down Expand Up @@ -766,10 +766,22 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>

<activity
android:name=".activity.style.DraggableMarkerActivity"
android:description="@string/description_draggable_marker"
android:label="@string/activity_draggable_maker">
<meta-data
android:name="@string/category"
android:value="@string/category_style" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.camera.GestureDetectorActivity"
android:label="@string/activity_gesture_detector"
android:description="@string/description_gesture_detector">
android:description="@string/description_gesture_detector"
android:label="@string/activity_gesture_detector">
<meta-data
android:name="@string/category"
android:value="@string/category_camera" />
Expand All @@ -789,14 +801,14 @@
android:name=".activity.espresso.EspressoTestActivity"
android:screenOrientation="portrait" />
<activity
android:name=".activity.espresso.DeviceIndependentTestActivity"
android:screenOrientation="portrait" />
android:name=".activity.espresso.DeviceIndependentTestActivity"
android:screenOrientation="portrait" />
<activity
android:name=".activity.style.FillExtrusionStyleTestActivity"
android:screenOrientation="portrait" />
<activity
android:name=".activity.render.RenderTestActivity"
android:screenOrientation="landscape"/>
android:screenOrientation="landscape" />
<!-- Configuration Settings -->
<meta-data
android:name="com.mapbox.TestEventsServer"
Expand All @@ -811,4 +823,4 @@
<!-- android:value="true" /> -->
</application>

</manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 282f89a

Please sign in to comment.