如何在 Google 地图中平滑移动当前位置?
How to smoothly move the current position in Google Maps?
在我的Android项目中,我需要当车辆移动时,标记位置也应该平滑移动,但是当位置改变时,标记从一个位置跳到另一个位置,它没有移动顺利。
我搜索了很多,但没有得到结果。
我不知道最好的方法是什么。请多多指教。
非常感谢
更新
@AnkitMehta 谢谢你的回答
MapsActivity 中的所有代码:
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(30000);
//mLocationRequest.setFastestInterval(30000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_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;
if (mCurrLocationMarker != null) {
mCurrLocationMarker.remove();
}
//Place current location marker
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
animateMarker(latLng, latLng, true);
//move map camera
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng,11));
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 11));
}
public static final int MY_PERMISSIONS_REQUEST_LOCATION = 99;
private void checkLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
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) {
//Prompt the user once explanation has been shown
ActivityCompat.requestPermissions(MapsActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSIONS_REQUEST_LOCATION );
}
})
.create()
.show();
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSIONS_REQUEST_LOCATION );
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_LOCATION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// location-related task you need to do.
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
if (mGoogleApiClient == null) {
buildGoogleApiClient();
}
mMap.setMyLocationEnabled(true);
}
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
Toast.makeText(this, "permission denied", Toast.LENGTH_LONG).show();
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
//This methos is used to move the marker of each car smoothly when there are any updates of their position
public void animateMarker(final LatLng startPosition, final LatLng toPosition,
final boolean hideMarker) {
final Marker marker = mMap.addMarker(new MarkerOptions()
.position(startPosition)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.car)));
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
final long duration = 1000;
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 lng = t * toPosition.longitude + (1 - t)
* startPosition.longitude;
double lat = t * toPosition.latitude + (1 - t)
* startPosition.latitude;
marker.setPosition(new LatLng(lat, lng));
if (t < 1.0) {
// Post again 16ms later.
handler.postDelayed(this, 16);
} else {
if (hideMarker) {
marker.setVisible(false);
} else {
marker.setVisible(true);
}
}
}
});
}
}
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(latLng)
.build();
CameraUpdate cu = CameraUpdateFactory.newCameraPosition(cameraPosition);
map.animateCamera(cu);
this will move to the latlng selected with animation(Camera position
and camera update is used)
覆盖此方法:
@Override
public void onLocationChange(Location location) {
LatLng latLng = new LatLng(location.latitude, location.longitude);
refreshMapPosition(latLng, 45)
}
private void refreshMapPosition(LatLng pos, float angle) {
CameraPosition.Builder positionBuilder = new CameraPosition.Builder();
positionBuilder.target(pos);
positionBuilder.zoom(15f);
positionBuilder.bearing(angle);
positionBuilder.tilt(60);
map.animateCamera(CameraUpdateFactory.newCameraPosition(positionBuilder.build()));
}
这个覆盖的方法是在 GoogleMap 类 上实现的,地图变量是我们定义来加载 google 地图实例的变量。
请参考:https://developers.google.com/maps/documentation/android-sdk/views
在我的Android项目中,我需要当车辆移动时,标记位置也应该平滑移动,但是当位置改变时,标记从一个位置跳到另一个位置,它没有移动顺利。
我搜索了很多,但没有得到结果。
我不知道最好的方法是什么。请多多指教。
非常感谢
更新
@AnkitMehta 谢谢你的回答
MapsActivity 中的所有代码:
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(30000);
//mLocationRequest.setFastestInterval(30000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_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;
if (mCurrLocationMarker != null) {
mCurrLocationMarker.remove();
}
//Place current location marker
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
animateMarker(latLng, latLng, true);
//move map camera
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng,11));
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 11));
}
public static final int MY_PERMISSIONS_REQUEST_LOCATION = 99;
private void checkLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
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) {
//Prompt the user once explanation has been shown
ActivityCompat.requestPermissions(MapsActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSIONS_REQUEST_LOCATION );
}
})
.create()
.show();
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSIONS_REQUEST_LOCATION );
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_LOCATION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// location-related task you need to do.
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
if (mGoogleApiClient == null) {
buildGoogleApiClient();
}
mMap.setMyLocationEnabled(true);
}
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
Toast.makeText(this, "permission denied", Toast.LENGTH_LONG).show();
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
//This methos is used to move the marker of each car smoothly when there are any updates of their position
public void animateMarker(final LatLng startPosition, final LatLng toPosition,
final boolean hideMarker) {
final Marker marker = mMap.addMarker(new MarkerOptions()
.position(startPosition)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.car)));
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
final long duration = 1000;
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 lng = t * toPosition.longitude + (1 - t)
* startPosition.longitude;
double lat = t * toPosition.latitude + (1 - t)
* startPosition.latitude;
marker.setPosition(new LatLng(lat, lng));
if (t < 1.0) {
// Post again 16ms later.
handler.postDelayed(this, 16);
} else {
if (hideMarker) {
marker.setVisible(false);
} else {
marker.setVisible(true);
}
}
}
});
}
}
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(latLng)
.build();
CameraUpdate cu = CameraUpdateFactory.newCameraPosition(cameraPosition);
map.animateCamera(cu);
this will move to the latlng selected with animation(Camera position and camera update is used)
覆盖此方法:
@Override
public void onLocationChange(Location location) {
LatLng latLng = new LatLng(location.latitude, location.longitude);
refreshMapPosition(latLng, 45)
}
private void refreshMapPosition(LatLng pos, float angle) {
CameraPosition.Builder positionBuilder = new CameraPosition.Builder();
positionBuilder.target(pos);
positionBuilder.zoom(15f);
positionBuilder.bearing(angle);
positionBuilder.tilt(60);
map.animateCamera(CameraUpdateFactory.newCameraPosition(positionBuilder.build()));
}
这个覆盖的方法是在 GoogleMap 类 上实现的,地图变量是我们定义来加载 google 地图实例的变量。
请参考:https://developers.google.com/maps/documentation/android-sdk/views