I have added google maps SDK to my project and have added a marker at the current position. If the user moves the camera the marker also changes its position by setting marker.setPosition(lat, long); in onCameraMove() callback, But I am getting a flicker when the marker changes its position. I want to make it smooth as in Ola app.
Attaching the code for reference
public class StoreAddressActivity extends BaseActivity<FragmentStoreAddressBinding, WebsiteCreationViewModel> implements GoogleMap.OnMyLocationButtonClickListener,
GoogleMap.OnMyLocationClickListener, OnMapReadyCallback, ActivityCompat.OnRequestPermissionsResultCallback,
GoogleMap.OnMarkerClickListener, OnCameraMoveListener, GoogleMap.OnCameraIdleListener, Navigator.StoreAddressNavigator {
private static final String TAG = StoreAddressActivity.class.getSimpleName();
@Inject
ViewModelProviderFactory factory;
private WebsiteCreationViewModel websiteCreationViewModel;
private FragmentStoreAddressBinding fragmentStoreAddressBinding;
private FusedLocationProviderClient mFusedLocationClient;
private Location mLastLocation;
private UiSettings mUiSettings;
private Marker currentLocationMarker;
private static final int DEFAULT_ZOOM = 15;
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
private boolean locationPermissionGranted = false;
private GoogleMap map;
private StringBuilder mResult;
private int previousLength;
private boolean backSpace;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentStoreAddressBinding = getViewDataBinding();
fragmentStoreAddressBinding.setNavigator(this);
init();
}
private void init() {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
String apiKey = getString(R.string.google_maps_key);
if (!Places.isInitialized()) {
Places.initialize(getApplicationContext(), apiKey);
}
PlacesClient placesClient = Places.createClient(this);
fragmentStoreAddressBinding.mapLocationEdittext.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
previousLength = s.length();
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
backSpace = previousLength > s.length();
if (!backSpace) {
// Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest,
// and once again when the user makes a selection (for example when calling fetchPlace()).
AutocompleteSessionToken token = AutocompleteSessionToken.newInstance();
// Create a RectangularBounds object.
RectangularBounds bounds = RectangularBounds.newInstance(
new LatLng(-33.880490, 151.184363), //dummy lat/lng
new LatLng(-33.858754, 151.229596));
// Use the builder to create a FindAutocompletePredictionsRequest.
FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
// Call either setLocationBias() OR setLocationRestriction().
//.setLocationBias(bounds)
//.setLocationRestriction(bounds)
.setCountry("IN")
.setTypeFilter(TypeFilter.ADDRESS)
.setSessionToken(token)
.setQuery(s.toString())
.build();
placesClient.findAutocompletePredictions(request).addOnSuccessListener(response -> {
mResult = new StringBuilder();
for (AutocompletePrediction prediction : response.getAutocompletePredictions()) {
mResult.append(" ").append(prediction.getFullText(null) + "\n");
Log.d(TAG, prediction.getPlaceId());
Log.d(TAG, prediction.getPrimaryText(null).toString());
}
Log.d(TAG, mResult.toString());
// mSearchResult.setText(String.valueOf(mResult));
}).addOnFailureListener((exception) -> {
if (exception instanceof ApiException) {
ApiException apiException = (ApiException) exception;
Log.e(TAG, "Place not found: " + apiException.getStatusCode());
}
});
}
}
});
}
@Override
public WebsiteCreationViewModel getViewModel() {
websiteCreationViewModel = new ViewModelProvider(this, factory).get(WebsiteCreationViewModel.class);
return websiteCreationViewModel;
}
@Override
public int getBindingVariable() {
return BR.viewModel;
}
@Override
public int getLayoutId() {
return R.layout.fragment_store_address;
}
@Override
public boolean onMyLocationButtonClick() {
return false;
}
@Override
public void onMyLocationClick(@NonNull Location location) {
}
@Override
public void onMapReady(GoogleMap googleMap) {
map = googleMap;
mUiSettings = map.getUiSettings();
mUiSettings.setZoomControlsEnabled(true);
mUiSettings.setMyLocationButtonEnabled(false);
map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
map.setOnMarkerClickListener(this);
map.setOnCameraMoveListener(this);
map.setOnCameraIdleListener(this);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
locationPermissionGranted = true;
map.setMyLocationEnabled(true);
} else {
//Request Location Permission
checkLocationPermission();
}
} else {
locationPermissionGranted = true;
map.setMyLocationEnabled(true);
}
getDeviceLocation();
}
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(StoreAddressActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE);
}
})
.create()
.show();
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE);
}
}
}
/**
* Enables the My Location layer if the fine location permission has been granted.
*/
private void enableMyLocation() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
locationPermissionGranted = true;
if (map != null) {
map.setMyLocationEnabled(true);
}
} else {
// Permission to access the location is missing. Show rationale and request permission
requestPermissionsSafely(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) {
return;
}
if (PermissionUtils.isPermissionGranted(permissions, grantResults, Manifest.permission.ACCESS_FINE_LOCATION)) {
// Enable the my location layer if the permission has been granted.
locationPermissionGranted = true;
enableMyLocation();
} else {
locationPermissionGranted = false;
}
}
@Override
public void onPause() {
super.onPause();
}
/**
* Demonstrates converting a {@link Drawable} to a {@link BitmapDescriptor},
* for use as a marker icon.
*/
private BitmapDescriptor vectorToBitmap(@DrawableRes int id) {
Drawable vectorDrawable = ResourcesCompat.getDrawable(getResources(), id, null);
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
vectorDrawable.draw(canvas);
return BitmapDescriptorFactory.fromBitmap(bitmap);
}
private void getDeviceLocation() {
/*
* Get the best and most recent location of the device, which may be null in rare
* cases when a location is not available.
*/
try {
if (locationPermissionGranted) {
Task<Location> locationResult = mFusedLocationClient.getLastLocation();
locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {
@Override
public void onComplete(@NonNull Task<Location> task) {
if (task.isSuccessful()) {
// Set the map's camera position to the current location of the device.
mLastLocation = task.getResult();
if (mLastLocation != null) {
LatLng latLng = new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude());
LocationAddress locationAddress = new LocationAddress();
locationAddress.getAddressFromLocation(mLastLocation.getLatitude(), mLastLocation.getLongitude(),
getApplicationContext(), new GeocoderHandler());
// currentLocationMarker = map.addMarker(new MarkerOptions()
// .position(latLng)
// .icon(vectorToBitmap(R.drawable.ic_location_marker))
// .title("MOVE PIN TO ADJUST")
// .draggable(false));
MarkerOptions markerOpt = new MarkerOptions();
markerOpt.position(latLng)
.title("MOVE PIN TO ADJUST").icon(vectorToBitmap(R.drawable.ic_location_marker));
//Set Custom InfoWindow Adapter
CustomInfoWindowAdapter adapter = new CustomInfoWindowAdapter(StoreAddressActivity.this);
map.setInfoWindowAdapter(adapter);
currentLocationMarker = map.addMarker(markerOpt);
currentLocationMarker.showInfoWindow();
map.moveCamera(CameraUpdateFactory.newLatLngZoom(
latLng, DEFAULT_ZOOM));
}
}
}
});
}
} catch (SecurityException e) {
}
}
@Override
public boolean onMarkerClick(Marker marker) {
return false;
}
@Override
public void onCameraMove() {
LatLng target = map.getCameraPosition().target;
//currentLocationMarker.setPosition(target);
animateMarker(currentLocationMarker, target, false);
// CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(target, DEFAULT_ZOOM);
// map.animateCamera(cameraUpdate, 4000, null);
}
@Override
public void onCameraIdle() {
Log.d(TAG, "Camera is idle");
LatLng target = map.getCameraPosition().target;
if (currentLocationMarker != null)
currentLocationMarker.setPosition(target);
LocationAddress locationAddress = new LocationAddress();
locationAddress.getAddressFromLocation(target.latitude, target.longitude,
getApplicationContext(), new GeocoderHandler());
fragmentStoreAddressBinding.currentLocationTextview.setText(getString(R.string.go_to_current_location));
}
@Override
public void currentLocationClick() {
if (mLastLocation != null) {
fragmentStoreAddressBinding.currentLocationTextview.setText(getString(R.string.this_is_current_location));
LatLng latLng = new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude());
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM);
map.moveCamera(cameraUpdate);
map.animateCamera(cameraUpdate, 4000, null);
LocationAddress locationAddress = new LocationAddress();
locationAddress.getAddressFromLocation(mLastLocation.getLatitude(), mLastLocation.getLongitude(),
getApplicationContext(), new GeocoderHandler());
}
}
private class GeocoderHandler extends Handler {
@Override
public void handleMessage(Message message) {
String locationAddress;
switch (message.what) {
case 1:
Bundle bundle = message.getData();
locationAddress = bundle.getString("address");
break;
default:
locationAddress = null;
}
fragmentStoreAddressBinding.mapLocationEdittext.setText(locationAddress);
}
}
public void animateMarker(final Marker marker, final LatLng toPosition,
final boolean hideMarker) {
if(currentLocationMarker == null)
return;
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
Projection proj = map.getProjection();
Point startPoint = proj.toScreenLocation(marker.getPosition());
final LatLng startLatLng = proj.fromScreenLocation(startPoint);
final long duration = 250;
final LinearInterpolator 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)
// * startLatLng.longitude;
// double lat = t * toPosition.latitude + (1 - t)
// * startLatLng.latitude;
marker.setPosition(new LatLng(toPosition.latitude, toPosition.longitude));
// if (t < 1.0) {
// // Post again 16ms later.
// handler.postDelayed(this, 16);
// } else {
// if (hideMarker) {
// marker.setVisible(false);
// } else {
// marker.setVisible(true);
// }
// }
}
});
}