How to know when a user moves the map in google map? - android

I have a very simple app which has a single activity. Everytime I start the app, the camera is moved to my position. I have used a LocationCallback so everytime I change my position, the camera follows me:
locationCallback = new LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult == null) {
return;
}
for (Location location : locationResult.getLocations()) {
currentLocation = location;
LatLng latLng = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
if (isFollowing) {
moveCamera(latLng, DEFAULT_ZOOM);
}
}
}
};
As you can see, I have a Boolean isFollowing which is set by default to true. The problem is when the user tries moves the map to another point, the camera starts following again.
I also have added on click listener to the LocationButton, to be sure that in the moment in which the user clicks it, to be followed by the camera.
mMap.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener(){
#Override
public boolean onMyLocationButtonClick()
isFollowing = true;
return false;
}
});
The question is, how can I set isFollowing to flase, so the camera stops following the user when he moved the map?
I found some posts here on SOF but only with OnMyLocationChangeListener, which I see is deprecated.

You can use the GoogleMap.OnCameraMoveStartedListener and GoogleMap.OnCameraIdleListener. See some examples here.
In your case:
mMap.setOnCameraMoveStartedListener(this);
mMap.setOnCameraIdleListener(this);
...
#Override
public void onCameraMoveStarted(int reason) {
if (reason == OnCameraMoveStartedListener.REASON_GESTURE) {
isFollowing = false;
}
}
#Override
public void onCameraIdle() {
// You may want to set isFollowing = true here
}

For future visitors, I have solved this problem by creating an invisible overlay in my layout file. I have added a simple TextView like this:
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/invisible_text_view"/>
And then in my activity is have used setOnTouchListener and managed to change the value of the isFollowing variable to false, like this:
View invisibleTextView = findViewById(R.id.invisible_text_view);
invisibleTextView.bringToFront();
invisibleTextView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
isFollowing = false;
return false;
}
});

Related

OnCameraChangeListener() is deprecated

Today, looking back at my old code, I've found out that OnCameraChangeListener() is now deprecated.
I'm finding difficult to understand how to fix this piece of code of mine:
mGoogleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
// Cleaning all the markers.
if (mGoogleMap != null) {
mGoogleMap.clear();
}
mPosition = cameraPosition.target;
mZoom = cameraPosition.zoom;
if (mTimerIsRunning) {
mDragTimer.cancel();
}
mDragTimer.start();
mTimerIsRunning = true;
}
});
The new listener (aka OnCameraMoveListener()) method onCameraMove() doesn't have a CameraPosition cameraPosition input variable, so I'm pretty lost: is there a way to recycle my old code?
Here are some references.
In play-services-maps 9.4.0 version of the API, They replaced GoogleMap.OnCameraChangeListener() with three camera listeners :
GoogleMap.OnCameraMoveStartedListener
GoogleMap.OnCameraMoveListener
GoogleMap.OnCameraIdleListener
Based on your code, I think you need to use GoogleMap.OnCameraIdleListener and GoogleMap.OnCameraMoveStartedListener like this:
mGoogleMap.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() {
#Override
public void onCameraMoveStarted(int i) {
mDragTimer.start();
mTimerIsRunning = true;
}
});
mGoogleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
#Override
public void onCameraIdle() {
// Cleaning all the markers.
if (mGoogleMap != null) {
mGoogleMap.clear();
}
mPosition = mGoogleMap.getCameraPosition().target;
mZoom = mGoogleMap.getCameraPosition().zoom;
if (mTimerIsRunning) {
mDragTimer.cancel();
}
}
});
In the new model for camera change events, you are correct that the CameraPosition is not passed into the listener.
Instead, you should just use getCameraPosition() whenever you specifically need it (i.e., when the move starts, mid-move, canceled, or finished/returned to idle).
onnCameraChangeListener() is deprecated now, you can use
mMap.setOnCameraMoveStartedListener { reason: Int ->
when (reason) {
GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE -> {
Log.d("camera", "The user gestured on the map.")
}
GoogleMap.OnCameraMoveStartedListener
.REASON_API_ANIMATION -> {
Log.d("camera", "The user tapped something on the map.")
}
GoogleMap.OnCameraMoveStartedListener
.REASON_DEVELOPER_ANIMATION -> {
Log.d("camera", "The app moved the camera.")
}
}
}
mMap.setOnCameraIdleListener {
val midLatLng: LatLng = mMap.cameraPosition.target//map's center position latitude & longitude
}
mMap.setOnCameraMoveStartedListener {
}
Here mMap is GoogleMap object and I am calling it inside
override fun onMapReady(map: GoogleMap?) {
mMap = map as GoogleMap
//your stuff
}
It is advisable to use newly introduced four camera listeners (OnCameraIdleListener, OnCameraMoveListener, OnCameraMoveStartedListener,OnCameraMoveCanceledListener), but if you still want to go with setOnCameraChangeListener use specific version of android-maps-utils(Given below)
compile 'com.google.maps.android:android-maps-utils:0.4.3'
in your module level gradle file.
use mGoogleMap.setOnCameraIdleListener instead of mGoogleMap.setOnCameraChangeListener.
mGoogleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
#Override
public void onCameraIdle() {
mPosition = mGoogleMap.getCameraPosition().target;
mZoom = mGoogleMap.getCameraPosition().zoom;
double lat = mGoogleMap.getCameraPosition().target.latitude;
double lng = mGoogleMap.getCameraPosition().target.longitude;
}
});

infoWindow's image not showing on first click, but it works on second click

My android using the Google map android API,InfoWindow's image not showing on first click, but it works on second click
I customize the infoWindow using
void setMapInfoWindow(){
mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
#Override
public View getInfoWindow(Marker arg0) {
return null;
}
#Override
public View getInfoContents(Marker arg0) {
View v = getLayoutInflater().inflate(R.layout.windowlayout, null);
final ImageView img = (ImageView)v.findViewById(R.id.imageView3);
//image
Picasso.with(context).load("http://imgurl").resize(140,
}
});
}
Here is my marker set up process
void setMarkers(){
...
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject datas=jsonArray.getJSONObject(i);
MarkerOptions tmp=new MarkerOptions()
.title("name")
.alpha(0.6f)
.position(new LatLng(123,456));//replace LatLng with sample
marker=mMap.addMarker(tmp);
}
....
setMapInfoWindow();
}
After I complete the Marker's setting, I call the setMapInfoWindow() function.
It work on my smartphone, but when you click the infoWindow on first time, It would not show the image ; but it showing when second click.
I test for some cases:
replace the web image to local image, the problem still occur.
store all marker into ArrayList, after all process completed, set all markers to showInfoWindow() , then set all markers to hideInfoWindow(). It works, but there are a infoWindow cannot be closed(the final one).
I'm trying use the Bitmap to get the image, But It not showing image, I trying a lot of ways from stackoverflow. But it work when using the Picasso library.
Thanks
the problem solved by:
it seems the google web service's image URL will be changed to another URL when loading.
example:
https://maps.googleapis.com/maps/api/place/photo?photoreference=
it will be changed to following URL by google:
https://lh4.googleusercontent.com/......
so I change the boolean not_first_time_showing_info_window to int,and callback three times
int not_first_time_showing_info_window=0;
//show image
try {
if(not_first_time_showing_info_window==2){
not_first_time_showing_info_window=0;
Picasso.with(HomeActivity.this).load("http://....").resize(600,400).into(img);
}
else if (not_first_time_showing_info_window==1) {
not_first_time_showing_info_window++;
Picasso.with(HomeActivity.this).load("http://....").resize(600, 400).into(img,new InfoWindowRefresher(marker));
}else if(not_first_time_showing_info_window==0){
not_first_time_showing_info_window++;
Picasso.with(HomeActivity.this).load("http://....").resize(600,400).into(img,new InfoWindowRefresher(marker));
}
} catch (Exception e) {
img.setImageDrawable(null);
}
First you can make a custom callback class to implement the com.squareup.picasso.Callback:
private class InfoWindowRefresher implements Callback {
private Marker markerToRefresh;
private InfoWindowRefresher(Marker markerToRefresh) {
this.markerToRefresh = markerToRefresh;
}
#Override
public void onSuccess() {
markerToRefresh.showInfoWindow();
}
#Override
public void onError() {}
}
Second, declare a boolean variable in your activity:
boolean not_first_time_showing_info_window;
Third, implement the public View getInfoContents(Marker marker) method:
#Override
public View getInfoContents(Marker marker) {
View v = getLayoutInflater().inflate(R.layout.custom_window, null);
ImageView image = (ImageView)v.findViewById(R.id.image_view);
if (not_first_time_showing_info_window) {
Picasso.with(MainActivity.this).load("image_URL.png").into(image);
} else {
not_first_time_showing_info_window = true;
Picasso.with(MainActivity.this).load("image_URL.png").into(image, new InfoWindowRefresher(marker));
}
return v;
}
You can also visit this GitHub page for completed implementation.
Example that works to me with Kotlin and Glide with data binding:
class CustomInfoWindow(private val context: Context,
private val markerModels: List<MarkerModel>) : GoogleMap.InfoWindowAdapter {
private var binding = ViewInfoWindowBinding.inflate(LayoutInflater.from(context))
private val images: HashMap<Marker, Bitmap> = HashMap()
private val targets: HashMap<Marker, CustomTarget<Bitmap>> = HashMap()
private fun bind(marker: Marker) {
val markerModel = markerModels.first { it.name == marker.title }
val image = images[marker]
with(binding) {
tvName.text = markerModel.name
tvAddress.text = markerModel.address
if (image == null) {
Glide.with(context)
.asBitmap()
.load(markerModel.imageUrl)
.dontAnimate()
.into(getTarget(marker))
} else {
ivMarker.setImageBitmap(image)
}
}
}
override fun getInfoContents(marker: Marker): View {
bind(marker)
return binding.root
}
override fun getInfoWindow(marker: Marker): View {
bind(marker)
return binding.root
}
inner class InfoTarget(var marker: Marker) : CustomTarget<Bitmap>() {
override fun onLoadCleared(placeholder: Drawable?) {
images.remove(marker)
}
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
images[marker] = resource
marker.showInfoWindow()
}
}
private fun getTarget(marker: Marker): CustomTarget<Bitmap> {
var target = targets[marker]
if (target == null) {
target = InfoTarget(marker)
targets[marker] = target
}
return target
}
}
Next set custom info window adapter:
map.setInfoWindowAdapter(CustomInfoWindow(context, markerModels))
And at marker listener set as next:
map.setOnMarkerClickListener { marker ->
if (marker.isInfoWindowShown) {
marker.hideInfoWindow()
} else {
marker.showInfoWindow()
}
true
}
I think Google has been listening and here's the solution that works for me. While setting up the cluster,
getMap().setOnMapLoadedCallback(mOnMapLoaded);
And once the map gets loaded, all the markers can be retreived from the clusterManager,
private GoogleMap.OnMapLoadedCallback mOnMapLoaded = () -> {
LogUtil.i(TAG, "Map has been loaded.");
showInfoWindow();
};
private boolean showInfoWindow() {
final WorkHeader selected = mWorkContainer.getSelectedHeader();
Collection<Marker> markers = mClusterManager.getMarkerCollection().getMarkers();
for (Marker marker : markers) {
if (marker.getTitle().contains(selected.siteName)) {
if (marker.getTitle().contains(selected.siteAddress)) {
mClusterManager.onMarkerClick(marker);
return true;
}
}
}
return false;
}
i had the same problem using Glide rather than Picasso
but i made a simple solution i don't know how efficient it will be but it works fine with me , anyway i would like to share it with you
private Marker lastClicked;
private HashSet<String> markerIdSet=new HashSet<>();
#Override
public boolean onMarkerClick(Marker marker) {
lastClicked=marker;
if(!markerIdSet.contains(marker.getId()))
{
marker.showInfoWindow();
marker.hideInfoWindow();
Handler handler=new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
lastClicked.showInfoWindow();
}
},150);
markerIdSet.add(marker.getId());
return true;
}
else
{
marker.showInfoWindow();
return true;
}
}
so basically you will creat a HashSet of String (markerID)
if the HashSet does not contain that marker id so we add it to the HashSet, and call the marker.showInfoWindow(); to show the infowindow and marker.hideInfoWindow(); to hide it then using Handler object, code execution will wait 150ms and then show the infowindow calling marker.showInfoWindow(); again ,doing that you will get the photo from the first click.
technically you are forcing the code to click the marker twice automatically for the first time of each marker.
please let me know if it worked.

Android & Google Maps - close info window with back button

I have an activity that holds a fragment with Google Map view in it. App adds several dozens of markers to the MapView, using MarkerManager and ClusterRenderer to form clusters.
The problem is that when I have marker's InfoWindow opened and I press hardware Back button, it closes the app. Instead of that, I would like to have the InfoWindow closed.
Is there any straightforward way to achieve this?
I managed to solve the problem.
I modified MarkerManager to send notification via EventBus when InfoWindow is about to be opened:
#Override
public View getInfoContents(Marker marker) {
View content = fillContent();
EventBus.getDefault().post(new MapInfoWindowShownEvent(marker));
return content;
}
and I added event handling in the activity:
private Marker mLastShownInfoWindowMarker = null;
#Override
public void onBackPressed() {
if(mLastShownInfoWindowMarker != null && mLastShownInfoWindowMarker.isInfoWindowShown()) {
mLastShownInfoWindowMarker.hideInfoWindow();
} else {
super.onBackPressed();
}
}
public void onEvent(MapInfoWindowShownEvent event) {
mLastShownInfoWindowMarker = event.getMarker();
}
Using this information I decided to make it a bit simpler for myself:
private Marker mLastShownInfoWindowMarker = null;
public void setMLastShownInfoWindowMarker(Marker marker)
{this.mActiveMapMarker=marker;}
#Override
public void onBackPressed() {
if(mLastShownInfoWindowMarker != null && mLastShownInfoWindowMarker.isInfoWindowShown()) {
mLastShownInfoWindowMarker.hideInfoWindow();
} else {
super.onBackPressed();
}
}
Then the following where you have your mapfragment:
private MainActivity activity; // swap this to your activity
public MainActivityMapController(MainActivity activity) {
this.activity = activity;
}
// override markerclicklistener to store lastShownInfoWindowMarker in
// the activity where back button will be used
map.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
#Override
public boolean onMarkerClick(Marker marker) {
activity.setMLastShownInfoWindowMarker(marker);
return false; // false keeps the standard behavior
}
});

Refreshing makers (ClusterItems) in Google Maps v2 for Android

I'm using Google Maps Android API Utility Library and I'm downloading certain images from internet that I want to use as markers.
The way I'm doing it is like in the following snippet:
class MarkerItemClusterRenderer extends DefaultClusterRenderer<MarkerItem> {
...
#Override
protected void onBeforeClusterItemRendered(MarkerItem item,
final MarkerOptions markerOptions) {
super.onBeforeClusterItemRendered(item, markerOptions);
mImageLoader.get(item.getImageUrl(), new ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.i("XXX", error.toString());
}
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response != null && response.getBitmap() != null) {
mImageIcon.setImageBitmap(response.getBitmap());
Bitmap icon = mIconGenerator.makeIcon();
Bitmap bhalfsize = Bitmap.createScaledBitmap(icon, 150,
150, false);
markerOptions.icon(BitmapDescriptorFactory
.fromBitmap(bhalfsize));
}
}
});
}
The problem is, that when the image is downloaded, the map (and thus the marker) doesn't refresh, so most of the times (but not always) I still see the red default markers.
I tried to do mImageIcon.invalidate(); mImageIcon.requestLayout(); but there's still no luck.
Is there anyway to achieve this?
Thanks a lot in advance.
You just need to make all this stuff in
protected void onClusterItemRendered(T clusterItem, Marker marker) {
...
}
In onBeforeClusterItemRendered you set icon on MarkerOptions in async callback. At this time it could be added to map and become real Marker. So you icon will be set to already useless object.
That's why you need to do it in onClusterItemRendered
Let's say you have GoogleMap object declared as:
private GoogleMap mMap;
In onResponse() method before applying any change to marker, try writing following statement to clear previous markers:
mMap.clear();
Now set your new marker.
I might be a bit late but i write it down so it can be useful for somebody looking for a solution like i was.
Basically what you have to do is refresh the marker and not the ClusterItem, but i used my own ClusterItem implementation to store some important data.
So your code inside onBeforeClusterItemRendered becomes like this:
LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds; //take visible region on map
if(bounds.contains(item.getPosition()) && !item.hasImage()) { //if item is not inside that region or it has an image already don't load his image
mImageLoader.get(item.getImageUrl(), new ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.i("XXX", error.toString());
}
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response != null && response.getBitmap() != null) {
mImageIcon.setImageBitmap(response.getBitmap());
Bitmap icon = mIconGenerator.makeIcon();
Bitmap bhalfsize = Bitmap.createScaledBitmap(icon, 150,
150, false);
//Set has image flag
item.setHasImage(true);
//Find the right marker
MarkerManager.Collection markerCollection = mClusterManager.getMarkerCollection();
Collection<Marker> markers = markerCollection.getMarkers();
for (Marker m : markers) {
if (id.equals(m.getTitle())) {
//set the icon
m.setIcon(BitmapDescriptorFactory.fromBitmap(image));
break;
}
}
}
}
});
}
And your MyItem class must have some parameters which are useful for remember our stuff:
public class MyItem implements ClusterItem {
private String itemId;
private LatLng mPosition;
private WMWall wall;
private boolean hasImage = false;
public MyItem(double latitude, double longitude) {
mPosition = new LatLng(latitude, longitude);
}
#Override
public LatLng getPosition() {
return mPosition;
}
public WMWall getWall() {
return wall;
}
public void setWall(WMWall wall) {
this.wall = wall;
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public boolean hasImage() {
return hasImage;
}
public void setHasImage(boolean hasImage) {
this.hasImage = hasImage;
}
}
It is really important to load only the images of markers contained into bounds, otherwise you'll run into OOM.
And if the hasImage() method returns true we don't need to load the image again since it is already stored into the marker object.

detect maps v2 zoom by user not device(automatic)

I am using the code below to detect zoom by user but its in loop even if I dont touch anything .How do I detect human touch on device and check if he zoomed ? I really appreciate any help.Thanks in Advance.
googleMap.setOnCameraChangeListener(new OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition position) {
// TODO Auto-generated method stub
if (position.zoom < 12 ) {
googleMap.animateCamera(CameraUpdateFactory.zoomTo(13));
}
googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}
}
});
}
The endless loop is obvious.
Either you have a zoom level of 20, then you will always end up in the second if condition.
Or you have a zoom level less than 20, then you will always end up in the third if condition.
I would try something like this:
boolean changeTriggeredByProgram = false;
googleMap.setOnCameraChangeListener(new OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition position) {
if (changeTriggerByProgram) {
changeTriggeredByProgram = false;
return;
}
/* Check, whether you want to adapt the position by program,
* and if so, do the following: */
changeTriggeredByProgram = true;
googleMap.animateCamera(newPosition, new CancelableCallback() {
#Override
public void onFinish() {
}
#Override
public void onCancel() {
changeTriggeredByProgram = false;
}
};
}
}
No guarantee that this works. I am using a slightly different solution, as my problem is slightly different.

Categories

Resources