Waiting until the google map has size in a MapFragment - android

My activity contains a MapFragment in a LinearLayout. I do the following
in onCreate:
I inflate this layout using setContentView in the onCreate method of
my activity.
Get a handle to the GoogleMap using getMap().
in onStart:
I get some place coordinates from an SQLite Database
add corresponding markers to the map
add these points to a LatLngBounds.Builder
animate the camera using newLatLngBounds(Builder.build(), 10)
According to maps api reference, I shouldn't call newLatLngBounds(LatLngBounds bounds, int padding) before making sure that the map has a size. I indeed get an IllegalStateException at this point. But what is the right method for waiting until the map has a size?

The solution by rusmus didn't work for me. I used this one instead:
map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
#Override
public void onMapLoaded() {
map.animateCamera(cameraUpdate);
}
});
If you know the map size, you can avoid waiting and move the camera before the map is displayed. We finally used the display size as an approximation of the map size (but you could find out the exact size if you want to be more precise):
final DisplayMetrics display = getResources().getDisplayMetrics();
final int padding = display.widthPixels / 20;
final CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(
boundsBuilder.build(), display.widthPixels, display.heightPixels, padding);
map.moveCamera(cameraUpdate);

I have succesfully used the following code in the past:
final LatLngBounds.Builder builder = new LatLngBounds.Builder();
final View mapView = fragment.getView();
final GoogleMap map = fragment.getMap():
//Add points to builder. And get bounds...
final LatLngBounds bounds = builder.build();
// Pan to see all markers in view.
// Cannot zoom to bounds until the map has a size.
if (mapView.getViewTreeObserver().isAlive()) {
mapView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50), 1500, null);
}
});
}

With the latest GoogleServices you can use MapFragment.getMapAsync.
Directly from the docs
Sets a callback object which will be triggered when the GoogleMap instance is ready to be used.

Related

Maps is rendering a bit too slow when zoom in/out

I need some help to improve the performance of my algorithm, I'm struggling for days and can't find a good solution.
Goal: My app need to show a marker for each airport in the world (~1k markers), and each marker must show the airport name.
What I did: For the marker, I created an RelativeLayout with the Icon and a TextView to populate the name of aiport.
Also, I wrote a class "AirportCluster" extending DefaultClusterRenderer and inside the overrode method onBeforeClusterItemRendered I just cast the layout to Bitmap so it can be rendered on the map
#Override
protected void onBeforeClusterItemRendered(AirportItem airportItem, MarkerOptions markerOptions) {
RelativeLayout customLayoutMarker = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.custom_airport_marker,null);
String tAirportName = airportItem.getAirport().getAirportName().toLowerCase();
tAirportName = Character.toUpperCase(tAirportName.charAt(0)) + tAirportName.substring(1);
TextView textLabelAirportMarker = (TextView) customLayoutMarker.findViewById(R.id.label_text_airport_marker);
textLabelAirportMarker.setText(tAirportName);
customLayoutMarker.setDrawingCacheEnabled(true);
customLayoutMarker.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
customLayoutMarker.layout(0,0, customLayoutMarker.getMeasuredWidth(), customLayoutMarker.getMeasuredHeight());
customLayoutMarker.buildDrawingCache(true);
Bitmap flagBitmap;
if ( mMemoryCache.get(tAirportName) == null){
flagBitmap = Bitmap.createBitmap(customLayoutMarker.getDrawingCache());
mMemoryCache.put(tAirportName,flagBitmap);
}else{
flagBitmap = mMemoryCache.get(tAirportName);
}
BitmapDescriptor markerAirportWithText = BitmapDescriptorFactory.fromBitmap(flagBitmap);
markerOptions.icon(markerAirportWithText);
//markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.airport_marker));
}
As we can see in the code, I'm trying to cache it on the memory as well, but this is not working that good.
All the markers are being added on the method onMapReady inside a loop for each airport in the list
for (Airport temp : fixedAirports) {
LatLng latLng = new LatLng(temp.getCityLat(), temp.getCityLng());
// create marker
MarkerOptions markerOptions = new MarkerOptions().position(latLng).title("Marker in" + temp.getCityName());
markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.airport_marker));
AirportItem offsetItem = new AirportItem(temp.getCityLat(), temp.getCityLng());
offsetItem.setAirport(temp);
mClusterManager.addItem(offsetItem);
}
Issue: I also have a custom image for clustered airports, and when i try to zoom in/out sometimes it take to much to the clustered icon dismiss and show the marker item for an airport, sometimes the icon of the item even overlaps the clustered one, just like on the images below.
What I have to do to these animations (changing from clustered icon from single markers) be more fast and smooth??
Prepare BitmapDescriptors first in background (i.e. Coroutines viewModelScope or another thread or asynctask or Rxjava), then load them to ui. Dont draw anything inside your ClusterRenderer class. Doing so helped me a bit.

How to trigger an event when google map marker is dropped at a certain location on screen?

The google map marker can be dragged around the screen so I want to know if there's a way to trigger an event when the marker is dropped at a certain location on the screen, say, bottom left.
After layout created and map initialized add the following code in onCreate();
View mapView = getSupportFragmentManager().findFragmentById(R.id.map).getView();
if (mapView.getViewTreeObserver().isAlive()) {
mapView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(CENTER, 15));
Point markerScreenPosition = map.getProjection().toScreenLocation(marker.getPosition());
int x = markerScreenPosition.getX();
int y = markerScreenPosition.getY();
if(x == yourValue && y == yourValue){
//your trigger code goes here
}
}
});
}
I think markers are not automatically dropped. they are handled by your code. for example you can add a marker on map click or on map long click. get that event and try fixing

Not able to update tilt after getting marker bounds Gmap V2

I am using this to get the bounds of markers on my map:
markerList.add(m);
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (Marker marker : markerList) {
builder.include(marker.getPosition());
}
LatLngBounds bounds = builder.build();
CameraUpdate updatecamera = CameraUpdateFactory.newLatLngBounds(bounds, 50);
map.animateCamera(updatecamera);
After getting update, I would like to tilt the map to 30 degrees.
I have used this to tilt the camera in the past, but it is not working with the bounds feature:
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(meLoc).zoom(6).bearing(0).tilt(30).build();
map.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraPosition));
How do I add this tilt after getting the bounds?
Thanks
As you say, the Android Maps API v2 doesn't currently directly support animating the camera with both bounds and tilt info.
One workaround is to use the following steps:
Do the camera animation you want to perform with the bounds, but leave out the tilt
Listen for the end of the above camera animation, get the current camera position, and then perform the tilt
This has the effect of moving the camera to the bounds while looking straight down (i.e., tilt = 0), and then tilting the camera in place. I've found that this works well in some use cases, and actually looks nice, but in others its kind of awkward. It may or may not work for your particular use case.
To implement this, let's assume you have a main class MapScreen.java where your map is implemented. You'll need change it to include the camera listener interface, a reference to the camera position, a reference to your activity, an initial tilt value you want to use (you can alternately set this at runtime), and a default camera padding:
public class MapScreen extends FragmentActivity implements GoogleMap.OnCameraChangeListener {
...
private GoogleMap map;
public static CameraPosition lastCameraPosition;
public static MapScreen mapScreen;
public static float CAMERA_INITIAL_TILT = 30.0f;
public static int CAMERA_PADDING = 100;
...
public void onCreate(Bundle savedInstanceState) {
...
mapScreen = this;
...
//Set up map object here like normal
map = ((SupportMapFragment)(getSupportFragmentManager().findFragmentById(R.id.map))).getMap();
...
//Set camera change listener
map.setOnCameraChangeListener(this);
}
...
}
In this class, you'll also need to add a method to listen and save the location of the camera when the first animation stops:
#Override
public void onCameraChange(CameraPosition position) {
//Save the last camera position so we can reference it when tilting the camera following animations
lastCameraPosition = position;
}
Also, add an inner class you can reference that performs the tilt after the first animation finishes (with a slight delay to get the correct CameraPosition):
public class tiltOnFinishAnimation implements CancelableCallback {
#Override
public void onCancel() {
//Do nothing
}
#Override
public void onFinish() {
//We want to run the tilt animation, but the CameraPosition needed to center the camera in the right place
//is only available after the onFinish() method completes. So, we delay this by 10 ms to let the CameraPosition update
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
mapScreen.runOnUiThread(new Runnable() {
public void run() {
if(lastCameraPosition != null){
//Finish with a tilt
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(lastCameraPosition.target)
.zoom(lastCameraPosition.zoom)
.bearing(lastCameraPosition.bearing)
.tilt(CAMERA_INITIAL_TILT)
.build();
//Perform the tilt!
mapScreen.map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}
}
});
}
}, 10);
}
}
Finally, when you want to move the camera, execute your normal code, but first identify some of the map view attributes needed for animating the camera correctly based on orientation, and include a reference to the callback to execute when the first part of the animation without the tilt stops:
//Get the View height and width, so we don't exceed the screen size after padding for the camera updates
int width;
int height;
if(mapFragment != null){
width = mapFragment.getView().getWidth();
height = mapFragment.getView().getHeight();
}else{
//If the map fragment hasn't been instantiated yet, use the entire display
if (android.os.Build.VERSION.SDK_INT >= 13){
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
width = size.x;
height = size.y;
}else{
Display display = getWindowManager().getDefaultDisplay();
width = display.getWidth();
height = display.getHeight();
}
}
if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
//Use verticle size for padding
CAMERA_PADDING = (int) (height * 0.2f);
}else{
//Use horizontal size for padding
CAMERA_PADDING = (int) (width * 0.2f);
}
//Your code
markerList.add(m);
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (Marker marker : markerList) {
builder.include(marker.getPosition());
}
LatLngBounds bounds = builder.build();
//Here's the new code below that triggers first the movement to the bounds, then the tilt (as a callback)
map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, width, height, CAMERA_PADDING), new tiltOnFinishAnimation());
In order to set GoogleMap Tilt and Bound all marker you need to use following code.
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 75));
CameraPosition camPos = new CameraPosition.Builder(googleMap.getCameraPosition())
.target(bounds.getCenter())
.bearing(bearing).tilt(45).build();
googleMap.animateCamera(CameraUpdateFactory
.newCameraPosition(camPos));
The key point is not having an animateCamera(..) followed by another animateCamera(..) without enough delay, as they overlap. So you should better use moveCamera(..) first and then animateCamera(..), or use some delay technique for the second animateCamera(..) such as new Handler().postDelayed(..).

Google map camera position on multiple markers

I am using google map API 2 and showing multiple markers on it. My problem it that I want my camera to be positioned on all my markers.
I have done it for single marker as
myMap.moveCamera(CameraUpdateFactory.newLatLngZoom(MyLocation, 22));
I have Used Below Way to see all markers in view. It will Bound your Mapview including all the Lat-Long Location you have in your arraylist.
// Pan to see all markers in view.
// Cannot zoom to bounds until the map has a size.
final View mapView = getSupportFragmentManager().findFragmentById(R.id.map).getView();
if (mapView.getViewTreeObserver().isAlive()) {
mapView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#SuppressLint("NewApi")
#Override
public void onGlobalLayout() {
LatLngBounds.Builder bld = new LatLngBounds.Builder();
for (int i = 0; i < YOUR_ARRAYLIST.size(); i++) {
LatLng ll = new LatLng(YOUR_ARRAYLIST.get(i).getPos().getLat(), YOUR_ARRAYLIST.get(i).getPos().getLon());
bld.include(ll);
}
LatLngBounds bounds = bld.build();
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 70));
mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
Hope it Will Help.

Adjusting google map (api v2) zoom level in android

I'am using maps api v2 in my app. I have to show my current location and target location on the map such that both the locations are visible (at the greatest possible zoom level) on the screen. Here is what i have tried so far...
googleMap = ((SupportMapFragment) getFragmentManager().findFragmentById(R.id.mapFragment)).getMap();
if(googleMap != null){
googleMap.setMyLocationEnabled(true);
LatLng targetLocationLatLng = new LatLng(modelObject.getLattitude(), modelObject.getLongitude());
LatLng currentLocationLatLng = new LatLng(this.currentLocationLattitude, this.currentLocationLongitude);
googleMap.addMarker(new MarkerOptions().position(targetLocationLatLng).title(modelObject.getLocationName()).icon(BitmapDescriptorFactory.fromResource(R.drawable.location_icon)));
LatLngBounds bounds = new LatLngBounds(currentLocationLatLng, targetLocationLatLng);
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 3));
}
App is force closing due to the following :
java.lang.IllegalStateException: Map size should not be 0. Most likely, layout has not yet occured for the map view.
How can i get max possible zoom level? Please help me.
In my project I use the com.google.android.gms.maps.model.LatLngBounds.Builder
Adapted to your source code it should look something like this:
Builder boundsBuilder = new LatLngBounds.Builder();
boundsBuilder.include(currentLocationLatLng);
boundsBuilder.include(targetLocationLatLng);
// pan to see all markers on map:
LatLngBounds bounds = boundsBuilder.build();
googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 3));
A good method to avoid this problem is to use a ViewTreeObserver to the layout containing the map fragment and a listener, to ensure that the layout has first been initialised (and hasn't a width=0) using addOnGlobalLayoutListener, as below:
private void zoomMapToLatLngBounds(final LinearLayout layout,final GoogleMap mMap, final LatLngBounds bounds){
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds,OVERVIEW_MAP_PADDING));
}
});
}

Categories

Resources