Smooth User Location Tracking in Mapbox Android

452 Views Asked by At

I'm working on an Android map app with a location button that animates the camera to the user's location and follows their movement. However, when the user is in motion (e.g., driving), there's a noticeable jump in the map's position after the animation completes. This occurs because the user's location changes while the animation is in progress. I thought of updating dynamically the target value of the pending animation, however, I'm afraid this is not possible in Mapbox.

Here's a snippet of my code:

var isAnimating = false
    
val initialOptions = CameraOptions.Builder()
    .center(currentUserPosition)
    .build()
    
val animationOptions = MapAnimationOptions.Builder()
    .duration(600)
    .animatorListener(animationListener) // listener updates isAnimating flag
    .build()
    
map.easeTo(initialOptions, animationOptions)

location.addOnIndicatorPositionChangedListener {
    if (!isAnimating) {
        val options = CameraOptions.Builder()
            .center(it)
            .build()

        map.setCamera(options)
        gestures.focalPoint = map.pixelForCoordinate(it)
    }
}

I'm seeking advice on how to make the camera smoothly follow the user's movement without the noticeable jump after the animation finishes. Any insights or ideas would be greatly appreciated.

3

There are 3 best solutions below

3
Jaspalsinh Gohil On

to avoid the jump after the animation, you can update the camera's position in real-time based on the user's location updates.

 var isAnimating = false
var currentCameraOptions: CameraOptions? = null // save the current camera options

// Initialize the camera position with your initial options
val initialCameraOptions = CameraOptions.Builder()
    .target(currentUserPosition)
    .zoom(INITIAL_ZOOM_LEVEL) // Set initial zoom level
    .build()

val initialOptions = CameraOptions.Builder()
    .camera(initialCameraOptions)
    .build()

val animationOptions = MapAnimationOptions.Builder()
    .duration(500)
    .animatorListener(animationListener)
    .build()

map.easeTo(initialOptions, animationOptions)

location.addOnIndicatorPositionChangedListener { userLocation ->
    if (!isAnimating) {
        // Update the target LatLng of the currentCameraOptions
        currentCameraOptions = CameraOptions.Builder(currentCameraOptions)
            .target(userLocation)
            .build()

        val options = CameraOptions.Builder()
            .camera(currentCameraOptions)
            .build()

        map.setCamera(options)
        gestures.focalPoint = map.pixelForCoordinate(userLocation)
    }
}
1
Dinh Lam On

I think this issue occurred because you use addOnIndicatorPositionChangedListener (it will be trigger location change without any conditions), so maybe that affect to animation if animation not completed.

You can use LocationProvider and make the frequency updates by your logic, ex delay 200ms for each update.

An example to use LocationProvider can found at: https://github.com/mapbox/mapbox-maps-android/blob/main/app/src/main/java/com/mapbox/maps/testapp/examples/LocationComponentAnimationActivity.kt

Hope this can be help you.

2
kasali On

To achieve smooth camera following of the user's movement in your Android map app with Mapbox, you can use a combination of camera animation and continuous location updates. The key is to update the camera's target during the animation as the user's location changes. Here's a modified version of your code that should help you achieve this:

var isAnimating = false

// Initialize a CameraOptions with your initial position
val initialOptions = CameraOptions.Builder()
    .center(currentUserPosition)
    .build()

val animationOptions = MapAnimationOptions.Builder()
    .duration(600)
    .animatorListener(animationListener) // listener updates isAnimating flag
    .build()

// Start the initial camera animation
map.easeTo(initialOptions, animationOptions)

location.addOnIndicatorPositionChangedListener { newLocation ->
    if (!isAnimating) {
        // Update the camera's target without animation
        val options = CameraOptions.Builder()
            .center(newLocation)
            .build()
        
        // Update the camera position
        map.setCamera(options)
        
        // Update the focal point for gestures
        gestures.focalPoint = map.pixelForCoordinate(newLocation)
    } else {
        // If an animation is in progress, update the animation's target
        animationOptionsBuilder.center(newLocation)
        map.updateCameraAnimationOptions(animationOptionsBuilder.build())
    }
}

Here's an explanation of the changes made:

  1. We start with an initial camera animation to set the initial position of the camera. This animation will smoothly move the camera to the currentUserPosition.

  2. We listen for location updates using addOnIndicatorPositionChangedListener. When a new location is received, we check if there is no animation in progress. If there is no animation in progress, we immediately update the camera's target and focal point without animation.

  3. If there is an animation in progress, we update the animation's target by modifying the animationOptions and then call map.updateCameraAnimationOptions to update the animation's target. This will ensure that the animation smoothly transitions to the new location.

With this approach, your camera should smoothly follow the user's movement without the noticeable jump after the animation finishes, even when the user is in motion.