Android项目中Google Maps标记平滑移动实现方案咨询
Hey there! I see you're struggling with jumpy marker updates on Google Maps when your vehicle moves, and you want that smooth gliding effect instead. Let's walk through the complete fix for your MapsActivity code.
Key Issues in Your Current Code
- You're calling
animateMarker()but haven't implemented this method yet - Your LocationRequest interval was too large (even the commented 30s value would be way too slow for smooth movement)
- You're removing and re-adding the marker on each location update, which causes the jarring jump
Step-by-Step Fix
1. Adjust Location Update Frequency
First, let's set a more frequent location update interval to get enough data points for smooth animation. Update your onConnected() method:
@Override public void onConnected(@Nullable Bundle bundle) { mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(1000); // Update every 1 second mLocationRequest.setFastestInterval(500); // Fastest update at 500ms mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // Use high accuracy for vehicle movement if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); } }
Note: You can adjust the interval values based on your needs—smaller intervals mean smoother movement but more battery usage.
2. Implement the animateMarker Method
Add this method to your MapsActivity class. It uses a Handler to interpolate the marker's position between the old and new location over time, creating a smooth glide:
private void animateMarker(final Marker marker, final LatLng target, final boolean hideMarker) { final Handler handler = new Handler(); final long start = SystemClock.uptimeMillis(); final LatLng startLatLng = marker.getPosition(); final long duration = 1000; // Animation duration in milliseconds final Interpolator interpolator = new LinearInterpolator(); handler.post(new Runnable() { @Override public void run() { long elapsed = SystemClock.uptimeMillis() - start; float t = interpolator.getInterpolation((float) elapsed / duration); double lat = t * target.latitude + (1 - t) * startLatLng.latitude; double lng = t * target.longitude + (1 - t) * startLatLng.longitude; marker.setPosition(new LatLng(lat, lng)); if (t < 1.0) { // Post again 16ms later (~60fps for smooth animation) handler.postDelayed(this, 16); } else { if (hideMarker) { marker.setVisible(false); } else { marker.setVisible(true); } } } }); }
3. Update onLocationChanged to Reuse the Marker
Instead of deleting and re-creating the marker every time, we'll check if it exists—if it does, we animate it to the new location; if not, we create it first:
@Override public void onLocationChanged(Location location) { mLastLocation = location; LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); if (mCurrLocationMarker == null) { // Create marker if it doesn't exist MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(latLng); markerOptions.title("Current Position"); markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)); mCurrLocationMarker = mMap.addMarker(markerOptions); // Move camera to initial position mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15)); } else { // Animate existing marker to new location animateMarker(mCurrLocationMarker, latLng, false); // Optional: Move camera smoothly with the marker mMap.animateCamera(CameraUpdateFactory.newLatLng(latLng)); } }
Full Modified MapsActivity Code
Here's the complete code with all fixes applied, including the missing permission handling methods:
import android.Manifest; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.content.res.Resources; import android.location.Location; import android.os.Build; import android.os.Handler; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentActivity; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MapStyleOptions; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener { private GoogleMap mMap; SupportMapFragment mapFragment; LocationRequest mLocationRequest; GoogleApiClient mGoogleApiClient; Location mLastLocation; Marker mCurrLocationMarker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maps); // Obtain the SupportMapFragment and get notified when the map is ready to be used. mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); mapFragment.getMapAsync(this); } @Override protected void onPause() { super.onPause(); //stop Location updates when Activity is no longer active if (mGoogleApiClient != null) { LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); } } @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; try { // Customise the styling of the base map using a JSON object defined // in a raw resource file. boolean success = googleMap.setMapStyle( MapStyleOptions.loadRawResourceStyle( this, R.raw.style_json)); if (!success) { Log.e("LOG", "Style parsing failed."); } } catch (Resources.NotFoundException e) { Log.e("LOG", "Can't find style. Error: ", e); } //initialize Google play Services if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { // Location Permission already granted buildGoogleApiClient(); mMap.setMyLocationEnabled(true); } else { // Request Location Permission checkLocationPermission(); } } else { buildGoogleApiClient(); mMap.setMyLocationEnabled(true); } } protected synchronized void buildGoogleApiClient() { mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); mGoogleApiClient.connect(); } @Override public void onConnected(@Nullable Bundle bundle) { mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(1000); // Update every 1 second mLocationRequest.setFastestInterval(500); // Fastest update at 500ms mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); } } @Override public void onConnectionSuspended(int i) { } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { } @Override public void onLocationChanged(Location location) { mLastLocation = location; LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); if (mCurrLocationMarker == null) { // Create marker if it doesn't exist MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(latLng); markerOptions.title("Current Position"); markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)); mCurrLocationMarker = mMap.addMarker(markerOptions); // Move camera to initial position mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15)); } else { // Animate existing marker to new location animateMarker(mCurrLocationMarker, latLng, false); // Optional: Move camera smoothly with the marker mMap.animateCamera(CameraUpdateFactory.newLatLng(latLng)); } } private void animateMarker(final Marker marker, final LatLng target, final boolean hideMarker) { final Handler handler = new Handler(); final long start = SystemClock.uptimeMillis(); final LatLng startLatLng = marker.getPosition(); final long duration = 1000; // Animation duration in milliseconds final Interpolator interpolator = new LinearInterpolator(); handler.post(new Runnable() { @Override public void run() { long elapsed = SystemClock.uptimeMillis() - start; float t = interpolator.getInterpolation((float) elapsed / duration); double lat = t * target.latitude + (1 - t) * startLatLng.latitude; double lng = t * target.longitude + (1 - t) * startLatLng.longitude; marker.setPosition(new LatLng(lat, lng)); if (t < 1.0) { // Post again 16ms later (~60fps for smooth animation) handler.postDelayed(this, 16); } else { if (hideMarker) { marker.setVisible(false); } else { marker.setVisible(true); } } } }); } private void checkLocationPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) { new AlertDialog.Builder(this) .setTitle("Location Permission Needed") .setMessage("This app needs the Location permission, please accept to use location functionality") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { ActivityCompat.requestPermissions(MapsActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1); } }) .create() .show(); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case 1: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { if (mGoogleApiClient == null) { buildGoogleApiClient(); } mMap.setMyLocationEnabled(true); } } else { Toast.makeText(this, "Permission denied", Toast.LENGTH_LONG).show(); } return; } } } }
Additional Tips
- Battery Optimization: If you're worried about battery usage, adjust the
intervalandfastestIntervalvalues. For vehicle tracking, 1-2 seconds is a good balance between smoothness and battery life. - Camera Movement: The optional
mMap.animateCamera()call makes the camera follow the marker smoothly. Remove this line if you don't want the camera to move with the marker. - Testing: Use Android Studio's Location Simulator to test smooth movement by creating a custom route and playing it back.
内容的提问来源于stack exchange,提问作者elias sharafi

