Advanced Viewport with gestures
Android Examples App Available
This example code is part of the Maps SDK for Android Examples App, a working Android project available on GitHub. Android developers are encouraged to run the examples app locally to interact with this example in an emulator and explore other features of the Maps SDK.
See our Run the Maps SDK for Android Examples App tutorial for step-by-step instructions.
package com.mapbox.maps.testapp.examples.viewport
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.mapbox.android.gestures.RotateGestureDetector
import com.mapbox.android.gestures.ShoveGestureDetector
import com.mapbox.android.gestures.StandardScaleGestureDetector
import com.mapbox.api.directions.v5.models.DirectionsResponse
import com.mapbox.core.constants.Constants
import com.mapbox.geojson.LineString
import com.mapbox.maps.EdgeInsets
import com.mapbox.maps.ImageHolder
import com.mapbox.maps.MapInitOptions
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
import com.mapbox.maps.extension.style.expressions.dsl.generated.interpolate
import com.mapbox.maps.extension.style.layers.generated.lineLayer
import com.mapbox.maps.extension.style.layers.properties.generated.LineCap
import com.mapbox.maps.extension.style.layers.properties.generated.LineJoin
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
import com.mapbox.maps.extension.style.style
import com.mapbox.maps.plugin.LocationPuck2D
import com.mapbox.maps.plugin.PuckBearing
import com.mapbox.maps.plugin.gestures.OnRotateListener
import com.mapbox.maps.plugin.gestures.OnScaleListener
import com.mapbox.maps.plugin.gestures.OnShoveListener
import com.mapbox.maps.plugin.gestures.gestures
import com.mapbox.maps.plugin.locationcomponent.OnIndicatorPositionChangedListener
import com.mapbox.maps.plugin.locationcomponent.location
import com.mapbox.maps.plugin.viewport.ViewportStatus
import com.mapbox.maps.plugin.viewport.ViewportStatusObserver
import com.mapbox.maps.plugin.viewport.data.FollowPuckViewportStateBearing
import com.mapbox.maps.plugin.viewport.data.FollowPuckViewportStateOptions
import com.mapbox.maps.plugin.viewport.data.OverviewViewportStateOptions
import com.mapbox.maps.plugin.viewport.data.ViewportOptions
import com.mapbox.maps.plugin.viewport.state.FollowPuckViewportState
import com.mapbox.maps.plugin.viewport.state.OverviewViewportState
import com.mapbox.maps.plugin.viewport.state.ViewportState
import com.mapbox.maps.plugin.viewport.viewport
import com.mapbox.maps.testapp.R
import com.mapbox.maps.testapp.examples.annotation.AnnotationUtils
import com.mapbox.maps.testapp.utils.SimulateRouteLocationProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Showcase the usage of viewport plugin with advanced gestures customisation.
*
* Touch the map to toggle the following and overview mode.
*
* @see [User location guide](https://docs.mapbox.com/android/maps/guides/user-location/#location-tracking)
*/
class AdvancedViewportGesturesExample : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var routePoints: LineString
private val followPuckViewportState: FollowPuckViewportState by lazy {
mapView.viewport.makeFollowPuckViewportState(
FollowPuckViewportStateOptions.Builder()
.bearing(FollowPuckViewportStateBearing.Constant(0.0))
.padding(EdgeInsets(200.0 * resources.displayMetrics.density, 0.0, 0.0, 0.0))
.build()
)
}
private val overviewViewportState: OverviewViewportState by lazy {
mapView.viewport.makeOverviewViewportState(
OverviewViewportStateOptions.Builder()
.geometry(routePoints)
.padding(EdgeInsets(100.0, 100.0, 100.0, 100.0))
.build()
)
}
private val onIndicatorPositionChangedListener = OnIndicatorPositionChangedListener {
mapView.gestures.focalPoint = mapView.mapboxMap.pixelForCoordinate(it)
}
private val viewportStatusObserver = ViewportStatusObserver { from, to, _ ->
// Clean up the gestures settings when current viewport is moving away from followPuckViewportState
if (from == ViewportStatus.State(followPuckViewportState)) {
clearAdvancedGesturesForFollowPuckViewportState()
}
// Set up the gestures settings when current viewport has entered the followPuckViewportState
if (to == ViewportStatus.State(followPuckViewportState)) {
setupAdvancedGesturesForFollowPuckViewportState()
}
}
private val onScaleListener = object : OnScaleListener {
override fun onScaleBegin(detector: StandardScaleGestureDetector) {
// set the default zoom that will be generated for camera following frames to null,
// thus allows gestures to adjust the camera zoom using zoom gestures.
followPuckViewportState.apply {
options = options.toBuilder().zoom(null).build()
}
}
override fun onScale(detector: StandardScaleGestureDetector) {
// no-ops
}
override fun onScaleEnd(detector: StandardScaleGestureDetector) {
// set the default zoom to current value on scale gesture end
followPuckViewportState.apply {
options = options.toBuilder().zoom(mapView.mapboxMap.cameraState.zoom).build()
}
}
}
private val onRotateListener = object : OnRotateListener {
override fun onRotateBegin(detector: RotateGestureDetector) {
// set the default bearing that will be generated for camera following frames to null,
// thus allows gestures to adjust the camera bearing using rotate gestures.
followPuckViewportState.apply {
options = options.toBuilder().bearing(null).build()
}
}
override fun onRotate(detector: RotateGestureDetector) {
// no-ops
}
override fun onRotateEnd(detector: RotateGestureDetector) {
// set the default bearing to current value on rotate gesture end
followPuckViewportState.apply {
options = options.toBuilder()
.bearing(FollowPuckViewportStateBearing.Constant(mapView.mapboxMap.cameraState.bearing))
.build()
}
}
}
private val onShoveListener = object : OnShoveListener {
override fun onShoveBegin(detector: ShoveGestureDetector) {
// set the default pitch that will be generated for camera following frames to null,
// thus allows gestures to adjust the camera pitch using shove gestures.
followPuckViewportState.apply {
options = options.toBuilder()
.pitch(null)
.build()
}
}
override fun onShove(detector: ShoveGestureDetector) {
// no-ops
}
override fun onShoveEnd(detector: ShoveGestureDetector) {
// set the default pitch to current value on shove gesture end
followPuckViewportState.apply {
options = options.toBuilder()
.pitch(mapView.mapboxMap.cameraState.pitch)
.build()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapView = MapView(this, MapInitOptions(this, styleUri = Style.TRAFFIC_DAY))
setContentView(mapView)
lifecycleScope.launch {
routePoints = withContext(Dispatchers.Default) {
LineString.fromPolyline(
DirectionsResponse.fromJson(
AnnotationUtils.loadStringFromAssets(
this@AdvancedViewportGesturesExample,
NAVIGATION_ROUTE_JSON_NAME
)
).routes()[0].geometry()!!,
Constants.PRECISION_6
)
}
mapView.mapboxMap.loadStyle(
style(Style.TRAFFIC_DAY) {
// Show the route line on the map
+geoJsonSource(GEOJSON_SOURCE_ID) {
geometry(routePoints)
}
+lineLayer(
ROUTE_LINE_LAYER_ID,
GEOJSON_SOURCE_ID
) {
lineColor(mapView.context.getColor(R.color.mapbox_blue))
lineWidth(10.0)
lineCap(LineCap.ROUND)
lineJoin(LineJoin.ROUND)
}
}
) {
// Prepare the location component with puck styling and a simulated route.
setupLocationComponent(routePoints)
// Observe the viewport status to setup/clean up the gestures settings
// specifically for followPuckViewportState
mapView.viewport.addStatusObserver(viewportStatusObserver)
// Switch ViewportStates by single tapping on the map.
mapView.gestures.addOnMapClickListener {
mapView.viewport.transitionTo(
when (mapView.viewport.status.getCurrentOrNextState()) {
followPuckViewportState -> overviewViewportState
else -> followPuckViewportState
}
)
false
}
mapView.viewport.transitionTo(overviewViewportState)
}
}
}
private fun setupAdvancedGesturesForFollowPuckViewportState() {
// Disable viewport's transitionsToIdleUponUserInteraction so that we can customise the gestures
// within the state.
mapView.viewport.options =
ViewportOptions.Builder().transitionsToIdleUponUserInteraction(false).build()
// Advanced gestures handling
// Disable scroll gesture
mapView.gestures.scrollEnabled = false
// Set the focal point of the gestures to the puck's location
mapView.location.addOnIndicatorPositionChangedListener(onIndicatorPositionChangedListener)
// Add hooks to the gesture to adjust the followPuckViewportState's options.
mapView.gestures.addOnScaleListener(onScaleListener)
mapView.gestures.addOnRotateListener(onRotateListener)
mapView.gestures.addOnShoveListener(onShoveListener)
}
private fun clearAdvancedGesturesForFollowPuckViewportState() {
// Re-enable the default viewport behaviour, which will transition to idle upon user interactions.
mapView.viewport.options =
ViewportOptions.Builder().transitionsToIdleUponUserInteraction(true).build()
// Stop updating the focal point to the puck's position, set the focal point to null(reset to the default behaviour)
mapView.location.removeOnIndicatorPositionChangedListener(onIndicatorPositionChangedListener)
mapView.gestures.focalPoint = null
// Re-enable scroll gesture.
mapView.gestures.scrollEnabled = true
// Remove the hooks to the gesture that updates the followPuckViewportState's options.
mapView.gestures.removeOnScaleListener(onScaleListener)
mapView.gestures.removeOnRotateListener(onRotateListener)
mapView.gestures.removeOnShoveListener(onShoveListener)
}
private fun setupLocationComponent(routePoints: LineString) {
// setup the location component
mapView.location.apply {
enabled = true
puckBearingEnabled = true
puckBearing = PuckBearing.COURSE
locationPuck = LocationPuck2D(
bearingImage = ImageHolder.from(R.drawable.mapbox_user_puck_icon),
scaleExpression = interpolate {
linear()
zoom()
stop {
literal(0.0)
literal(0.6)
}
stop {
literal(20.0)
literal(1.0)
}
}.toJson()
)
setLocationProvider(SimulateRouteLocationProvider(routePoints))
}
}
override fun onDestroy() {
super.onDestroy()
mapView.location.removeOnIndicatorPositionChangedListener(onIndicatorPositionChangedListener)
mapView.viewport.removeStatusObserver(viewportStatusObserver)
mapView.gestures.removeOnScaleListener(onScaleListener)
mapView.gestures.removeOnRotateListener(onRotateListener)
mapView.gestures.removeOnShoveListener(onShoveListener)
}
companion object {
private const val NAVIGATION_ROUTE_JSON_NAME = "navigation_route.json"
private const val GEOJSON_SOURCE_ID = "source_id"
private const val ROUTE_LINE_LAYER_ID = "route_line_layer_id"
}
}
private fun ViewportStatus.getCurrentOrNextState(): ViewportState? =
when (this) {
is ViewportStatus.State -> state
is ViewportStatus.Transition -> toState
ViewportStatus.Idle -> null
}
この{Type}は役に立ちましたか?