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;
}
});
Related
trying to update my google dependencies from 9.0.0 to 15.0.0. But I have to update
getMap();
with
getMapAsync();
Because getMap(); is deprecated in newest versions of google libraries.
The issue is I am using custom fragment to load the view. I am doing it this way because I have 3 different map types that can be loaded into the map view based on user selection. But it's not letting me do getMapAsync without error.
Here is my code:
private void setUpMapIfNeeded() {
if (mMapFragment == null) {
mMapFragment = new CustomSupportMapFragment();
mMapFragment.setOnMapCreatedListener(new CustomSupportMapFragment.MapViewCreatedListener() {
#Override
public void onMapCreated() {
boolean needUpdate = mMap == null;
mMap = mMapFragment.getMapAsync();
if (mMap != null)
setUpMap(needUpdate);
}
});
}
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.map, mMapFragment);
ft.commit();
}
But I am getting error:
getMapAsync (OnMapReadyCallback) in SupportMapFragment cannot be applied to ()
So I tried adding this, getContext() in like this:
getMapAsync(this);
getMapAsync(getContext));
but both come back with errors about it cannot be applied.
Any ideas? Sorry for my understanding of java is new and I am learning. I tried searching StackOverflow and google already for this issue but can only find resolutions when using regular supportmapfragment and not custom supportmapfragment.
Here is my setUpMap method if it helps for you to understand:
private void setUpMap(boolean needUpdate) {
boolean location_permission_granted = (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED);
if (!location_permission_granted) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 200);
} else {
mMap.setMyLocationEnabled(true);
}
mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
if (LOAD_PARTICULAR_MAP == 1) {
if (cameraPosition.zoom > AppConstants.MAX_ZOOM) {
CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(cameraPosition.target, AppConstants.MAX_ZOOM);
mMap.moveCamera(upd);
}
float maxZoom = 4f;
if (cameraPosition.zoom > maxZoom) {
mMap.animateCamera(CameraUpdateFactory.zoomTo(maxZoom));
return;
}
float minZoom = 0f;
if (cameraPosition.zoom < minZoom) {
mMap.animateCamera(CameraUpdateFactory.zoomTo(minZoom));
return;
}
}
}
});
mMap.setOnMapLongClickListener(this);
mMap.setOnMyLocationButtonClickListener(this);
mMap.setOnMapClickListener(this);
mMap.setOnMarkerDragListener(this);
mMap.setOnMarkerClickListener(this);
// mBtnMapType.setVisibility(View.VISIBLE);
mMap.getUiSettings().setScrollGesturesEnabled(true);
//mBtnLegend.setVisibility(View.VISIBLE);
//updateLegendButton();
updateMapType(mSettings.getMapType(GoogleMap.MAP_TYPE_TERRAIN));
/*mTileProvider = new TranslucentUrlTileProvider(AppConstants.SERVER_URL, AppConstants.SERVER_URL_FREE);
// mTileProvider.setPro(!mIsPro.showPurchaseDialog());
//mTileProvider.setOpacity(mOpacity);
mTileOverlay = mMap.addTileOverlay(new TileOverlayOptions().tileProvider(mTileProvider));*/
if (needUpdate)
if (LOAD_PARTICULAR_MAP != 4) {
gotoMyLocation();
}
// mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
// #Override
// public boolean onMarkerClick(Marker marker) {
// if (marker != null) {
// openGoogleMap(marker);
// return true;
// }
// return false;
// }
// });
switch (LOAD_PARTICULAR_MAP) {
case 1:
auroraOverMap();
mMap.animateCamera(CameraUpdateFactory.zoomTo(0f));
break;
case 2:
lightPollutionMap();
break;
case 3:
loadCloudMap();
break;
case 4:
highestValueView();
// mMap.animateCamera( CameraUpdateFactory.zoomTo( 12f ) );
break;
}
}
You can't get GoogleMap object by calling
mMap = mMapFragment.getMapAsync();
you should use code like that:
mMapFragment.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
}
});
where mMap is global variable:
public class MainActivity extends AppCompatActivity {
private GoogleMap mMap;
...
and after you got mMap you can use it for setup your map:
mMapFragment.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
if (mMap != null)
setUpMap(needUpdate);
}
});
Also, take a look at Official Tutorial.
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;
}
});
I am trying to get the LatLngBounds from the Visible Region on the device's screen after my Google map has been initialized. However I only receive a 0 values. My guess is that the map has not actually been loaded, even after OnMapReady has been called. I've looked all over for a better way of checking for map initialization and found nothing. How do I ensure I receive the correct data?
INITIALIZE MAP
public void initMap(){
MapFragment map = (MapFragment) getFragmentManager().findFragmentById(R.id.mapFragment);
map.getMapAsync(this);
}
ON MAP READY CALLBACK
#Override
public void onMapReady(GoogleMap googleMap) {
try {
if (googleMap != null) {
mGoogleMap = googleMap;
mGoogleMap.setOnMarkerClickListener(this);
mGoogleMap.setOnCameraChangeListener(this);
mGoogleMap.setOnMapClickListener(this);
mGoogleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
search();
}
} catch (Exception exception) {
mUtility.getThemedAlert(this,
getResources().getString(R.string.error_google_maps),
getResources().getString(R.string.unable_build_google_maps)).show();
}
}
GET BOUNDS
public void search() {
LatLngBounds bounds = mGoogleMap.getProjection().getVisibleRegion().latLngBounds;
Log.e("TEST", bounds.toString());
}
LOG OUTPUT
E/TEST﹕ LatLngBounds{southwest=lat/lng: (0.0,0.0), northeast=lat/lng: (0.0,0.0)}
OnMapLoaded function helps. Thank you.
Here is my code,
map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
#Override
public void onMapLoaded() {
invokeApi();
}
});
I'm using android maps utils for clustering the markers on google maps api v2. It worked fine, but when I added 2000+ markers, on max zoom it is still clustered (markers still have numbers):
Here is my method for filling map with markers:
public void getRiverData(String state, String type) {
URL = getResources().getString(R.string.base_url) + state + "/" + type
+ getResources().getString(R.string.end_url);
SimpleXmlRequest<XMLData> simpleRequest = new SimpleXmlRequest<XMLData>(
URL, XMLData.class, new Response.Listener<XMLData>() {
#Override
public void onResponse(XMLData response) {
// Initialize the manager with the context and the map.
// (Activity extends context, so we can pass 'this' in
// the constructor.)
mClusterManager = new ClusterManager<MarkerItem>(
getActivity(), map);
mClusterManager.setRenderer(new ClusterRenderer(
getActivity(), map, mClusterManager));
// response Object
waterItemsList = response.getNews();
for (News item : waterItemsList) {
if (item.getRiver_name() != null
&& item.getRiver_name() != "") {
water_level_value = item.getWater_level_value();
if (water_level_value != null
&& !water_level_value.equals(0)
&& !water_level_value.equals("")) {
MarkerItem offsetItem = new MarkerItem(item);
mClusterManager.addItem(offsetItem);
}
map.setOnMarkerClickListener(mClusterManager);
map.setInfoWindowAdapter(new InfoWindowAdapter() {
#Override
public View getInfoWindow(Marker marker) {
return null;
}
#Override
public View getInfoContents(Marker marker) {
try {
View v = getActivity()
.getLayoutInflater()
.inflate(
R.layout.marker_info,
null);
TextView title = (TextView) v
.findViewById(R.id.tvMarkerTitle);
TextView info = (TextView) v
.findViewById(R.id.tvMarkerInfo);
title.setText(marker.getTitle()
.toString());
info.setText(marker.getSnippet()
.toString());
return v;
} catch (Exception e) {
// kliknięcie w cluster
return null;
}
}
});
}
}
map.setOnCameraChangeListener(mClusterManager);
map.setOnInfoWindowClickListener(mClusterManager);
mClusterManager.cluster();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
// error Object
error.printStackTrace();
}
});
AppController.getInstance().addToRequestQueue(simpleRequest);
}
Can anyone help me? Why is it not working?
You can extend DefaultClusterRenderer class and set minimum markers to cluster.
public class InfoMarkerRenderer extends DefaultClusterRenderer<MyCustomMarker> {
public InfoMarkerRenderer(Context context, GoogleMap map, ClusterManager<MyCustomMarker> clusterManager) {
super(context, map, clusterManager);
//constructor
}
#Override
protected void onBeforeClusterItemRendered(final MyCustomMarker infomarker, MarkerOptions markerOptions) {
// you can change marker options
}
#Override
protected boolean shouldRenderAsCluster(Cluster cluster) {
return cluster.getSize() > 5; // if markers <=5 then not clustering
}
}
Via trail and error I found that if the markers are within ~10 feet (equivalent to 0.0000350º difference in lat or long), the markers don't decluster even at the max zoom level.
One way to solve for this problem is to implement a custom Renderer and let the app decide when to cluster. For example, the below code will cluster only when there are more than 1 marker and not at max Zoom. In other words it will decluster all markers at max zoom.
mClusterManager.setRenderer(new DefaultClusterRenderer<MyItem>(mContext, googleMap, mClusterManager) {
#Override
protected boolean shouldRenderAsCluster(Cluster cluster) {
if(cluster.getSize() > 1 && mCurrentZoom < mMaxZoom) {
return true;
} else {
return false;
}
}
});
To filter markers that have the same position, you could simply use a hashmasp, whose key is computed from the marker coordinates.
Something like:
Map<String, Marker> uniqueMarkers = new HashMap<String, Marker>();
for (Markers m : allMarkers) {
// Compute a key to filter duplicates
// You may need to account for small floating point precision errors by
// rounding those coordinates
String key = m.getLatitude() + "|" + m.getLongitude();
if (uniqueMarkers.get(key)!=null ) {
// Skip if we have a marker with the same coordinates
continue;
}
// Add marker and do something with it
uniqueMarkers.add(key, m);
// ...
}
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.