moverCámara con CameraUpdateFactory.newLatLngBounds lockings

Estoy haciendo uso de la nueva API de Android Google Maps .

Creo una actividad que incluye un MapFragment. En la actividad onResume , establezco los marcadores en el objeto GoogleMap y luego defino un cuadro delimitador para el mapa que incluye todos los marcadores.

Esto está usando el siguiente pseudo código:

 LatLngBounds.Builder builder = new LatLngBounds.Builder(); while(data) { LatLng latlng = getPosition(); builder.include(latlng); } CameraUpdate cameraUpdate = CameraUpdateFactory .newLatLngBounds(builder.build(), 10); map.moveCamera(cameraUpdate); 

La llamada a map.moveCamera() hace que mi aplicación se bloquee con la siguiente stack:

 Caused by: java.lang.IllegalStateException: Map size should not be 0. Most likely, layout has not yet at maps.am.rb(Unknown Source) at maps.yqa(Unknown Source) at maps.y.au.a(Unknown Source) at maps.y.ae.moveCamera(Unknown Source) at com.google.android.gms.maps.internal.IGoogleMapDelegate$Stub .onTransact(IGoogleMapDelegate.java:83) at android.os.Binder.transact(Binder.java:310) at com.google.android.gms.maps.internal.IGoogleMapDelegate$a$a .moveCamera(Unknown Source) at com.google.android.gms.maps.GoogleMap.moveCamera(Unknown Source) at ShowMapActivity.drawMapMarkers(ShowMapActivity.java:91) at ShowMapActivity.onResume(ShowMapActivity.java:58) at android.app.Instrumentation .callActivityOnResume(Instrumentation.java:1185) at android.app.Activity.performResume(Activity.java:5182) at android.app.ActivityThread .performResumeActivity(ActivityThread.java:2732) 

Si, en lugar del método de fábrica newLatLngBounds() , uso el método newLatLngZoom() , entonces no se produce la misma trampa.

¿Es el onResume el mejor lugar para dibujar los marcadores en el objeto GoogleMap o debería dibujar los marcadores y establecer la posición de la cámara en otro lugar?

Puede usar el método newLatLngBounds nuevo en OnCameraChangeListener . Todo funcionará perfectamente y no es necesario calcular el tamaño de la pantalla. Este evento ocurre después del cálculo del tamaño del mapa (según tengo entendido).

Ejemplo:

 map.setOnCameraChangeListener(new OnCameraChangeListener() { @Override public void onCameraChange(CameraPosition arg0) { // Move camera. map.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 10)); // Remove listener to prevent position reset on camera move. map.setOnCameraChangeListener(null); } }); 

Esas respuestas están bien, pero yo optaría por un enfoque diferente, uno más simple. Si el método solo funciona después de que el mapa esté listo, simplemente aguarde:

 map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30)); } }); 

OK, resolví esto. Como se documenta aquí, la API no se puede usar antes del diseño.

La API correcta para usar se describe como:

Nota: utilice únicamente el método más simple newLatLngBounds (límite, relleno) para generar un CameraUpdate si se va a usar para mover la cámara después de que el mapa haya sido diseñado. Durante el diseño, la API calcula los límites de visualización del mapa que se necesitan para proyectar correctamente el cuadro delimitador. En comparación, puede usar CameraUpdate devuelto por el método más complejo newLatLngBounds (límite, ancho, alto, relleno) en cualquier momento, incluso antes de que el mapa haya pasado por el diseño, porque la API calcula los límites de visualización de los argumentos que pasa.

Para solucionar el problema, calculé el tamaño de mi pantalla y proporcioné el ancho y la altura para

 public static CameraUpdate newLatLngBounds( LatLngBounds bounds, int width, int height, int padding) 

Esto me permitió especificar el diseño previo del cuadro delimitador.

La solución es más simple que eso …

 // Pan to see all markers in view. try { this.gmap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); } catch (IllegalStateException e) { // layout not yet initialized final View mapView = getFragmentManager() .findFragmentById(R.id.map).getView(); if (mapView.getViewTreeObserver().isAlive()) { mapView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @SuppressLint("NewApi") // We check which build version we are using. @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { mapView.getViewTreeObserver() .removeGlobalOnLayoutListener(this); } else { mapView.getViewTreeObserver() .removeOnGlobalLayoutListener(this); } gmap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); } }); } } 

IllegalStateException y use el oyente global en la vista. Si configura el tamaño encuadernado por adelantado (diseño previo) utilizando los otros métodos, significa que debe calcular el tamaño de THE VIEW, no el dispositivo de pantalla. Solo coinciden si va a pantalla completa con su mapa y no utiliza fragmentos.

Agregar y quitar marcadores se puede hacer antes de la finalización del diseño, pero mover la cámara no puede (excepto usar newLatLngBounds(boundary, padding) como se indica en la respuesta del OP).

Probablemente el mejor lugar para realizar una actualización inicial de la cámara es utilizar OnGlobalLayoutListener un solo OnGlobalLayoutListener como se muestra en el código de muestra de Google , por ejemplo, ver el siguiente extracto de setUpMap() en MarkerDemoActivity.java :

 // 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") // We check which build version we are using. @Override public void onGlobalLayout() { LatLngBounds bounds = new LatLngBounds.Builder() .include(PERTH) .include(SYDNEY) .include(ADELAIDE) .include(BRISBANE) .include(MELBOURNE) .build(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } else { mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); } }); } 

Esta es mi solución, solo espera hasta que el mapa esté cargado en ese caso.

 final int padding = getResources().getDimensionPixelSize(R.dimen.spacing); try { mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(pCameraBounds, padding)); } catch (IllegalStateException ise) { mMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(pCameraBounds, padding)); } }); } 

La respuesta aceptada no funcionaría para mí porque tenía que usar el mismo código en varios lugares de mi aplicación.

En lugar de esperar a que la cámara cambie, creé una solución simple basada en la sugerencia de Lee de especificar el tamaño del mapa. Esto es en caso de que su mapa tenga el tamaño de la pantalla.

 // Gets screen size int width = getResources().getDisplayMetrics().widthPixels; int height = getResources().getDisplayMetrics().heightPixels; // Calls moveCamera passing screen size as parameters map.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), width, height, 10)); 

Espero que ayude a alguien más!

La respuesta aceptada es, como se señala en los comentarios, un poco hacky. Después de implementarlo, noté una cantidad aceptable de registros de IllegalStateException en análisis. La solución que utilicé es agregar un OnMapLoadedCallback en el que se realiza CameraUpdate . La callback se agrega a GoogleMap . Una vez que el mapa se haya cargado por completo, se realizará la actualización de la cámara.

Esto hace que el mapa muestre brevemente la vista alejada (0,0) antes de realizar la actualización de la cámara. Sin embargo, creo que esto es más aceptable que causar accidentes o confiar en un comportamiento no documentado.

  map.moveCamera(CameraUpdateFactory.newLatLngZoom(bounds.getCenter(),10)); 

usar esto funcionó para mí

En casos muy raros, el diseño de MapView ha finalizado, pero el diseño de GoogleMap no está terminado, ViewTreeObserver.OnGlobalLayoutListener no puede detener el locking. Vi el colapso con el paquete de servicios de Google Play versión 5084032. Este raro caso puede ser causado por el cambio dynamic de la visibilidad de mi MapView.

Para resolver este problema, incorporé GoogleMap.OnMapLoadedCallback en onGlobalLayout() ,

 if (mapView.getViewTreeObserver().isAlive()) { mapView.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override @SuppressWarnings("deprecation") public void onGlobalLayout() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } else { mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } try { map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 5)); } catch (IllegalStateException e) { map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { Log.d(LOG_TAG, "move map camera OnMapLoadedCallback"); map.moveCamera(CameraUpdateFactory .newLatLngBounds(bounds, 5)); } }); } } }); } 

Utilicé un enfoque diferente que funciona para versiones recientes de Google Maps SDK (9.6+) y basado en onCameraIdleListener . Como veo hasta ahora, es el método de callback onCameraIdle llamado siempre después de onMapReady . Entonces, mi enfoque se parece a este fragmento de código (considerando que se puso en Activity ):

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // set content view and call getMapAsync() on MapFragment } @Override public void onMapReady(GoogleMap googleMap) { map = googleMap; map.setOnCameraIdleListener(this); // other initialization stuff } @Override public void onCameraIdle() { /* Here camera is ready and you can operate with it. you can use 2 approaches here: 1. Update the map with data you need to display and then set map.setOnCameraIdleListener(null) to ensure that further events will not call unnecessary callback again. 2. Use local boolean variable which indicates that content on map should be updated */ } 

Ok, estoy enfrentando el mismo problema. Tengo mi fragmento con SupportmapFragment, ABS y el cajón de navegación. Lo que hice fue:

 public void resetCamera() { LatLngBounds.Builder builderOfBounds = new LatLngBounds.Builder(); // Set boundaries ... LatLngBounds bounds = builderOfBounds.build(); CameraUpdate cu; try{ cu = CameraUpdateFactory.newLatLngBounds(bounds,10); // This line will cause the exception first times // when map is still not "inflated" map.animateCamera(cu); System.out.println("Set with padding"); } catch(IllegalStateException e) { e.printStackTrace(); cu = CameraUpdateFactory.newLatLngBounds(bounds,400,400,0); map.animateCamera(cu); System.out.println("Set with wh"); } //do the rest... } 

Y, por cierto, estoy llamando a resetCamera() desde onCreateView después de inflar y antes de volver.
Lo que hace es atrapar la excepción por primera vez (mientras que el mapa “obtiene un tamaño” como una forma de decirlo …) y luego, otras veces debo reiniciar la cámara, el mapa ya tiene tamaño y lo hace a través del relleno.

El problema se explica en la documentación , dice:

No cambie la cámara con esta actualización de la cámara hasta que el mapa haya sido diseñado (para que este método determine correctamente el cuadro delimitador y el nivel de zoom apropiados, el mapa debe tener un tamaño). De lo contrario, se IllegalStateException una IllegalStateException . NO es suficiente que el mapa esté disponible (es decir, getMap() devuelve un objeto no nulo); la vista que contiene el mapa también debe haber tenido un diseño tal que sus dimensiones hayan sido determinadas. Si no puede estar seguro de que esto ha ocurrido, utilice newLatLngBounds(LatLngBounds, int, int, int) lugar y proporcione las dimensiones del mapa de forma manual.

Creo que es una solución bastante decente. Espero que ayude a alguien.

Creé una manera de combinar las dos devoluciones de llamada: onMapReady y onGlobalLayout en una única observación que se emitirá solo cuando se hayan activado ambos eventos.

https://gist.github.com/abhaysood/e275b3d0937f297980d14b439a8e0d4a

Si necesita esperar a que se inicien las posibles interacciones del usuario, use OnMapLoadedCallback como se describe en las respuestas anteriores. Pero, si todo lo que necesita es proporcionar una ubicación predeterminada para el mapa, no hay necesidad de ninguna de las soluciones descritas en esas respuestas. Tanto MapFragment como MapView pueden aceptar un GoogleMapOptions al inicio que puede proporcionar la ubicación predeterminada de manera correcta. El único truco es no incluirlos directamente en su diseño porque el sistema los llamará sin las opciones, pero para inicializarlos dinámicamente.

Use esto en su diseño:

  

y reemplace el fragmento en su onCreateView() :

 GoogleMapOptions options = new GoogleMapOptions(); options.camera(new CameraPosition.Builder().target(location).zoom(15).build()); // other options calls if required SupportMapFragment fragment = (SupportMapFragment) getFragmentManager().findFragmentById(R.id.map); if (fragment == null) { FragmentTransaction transaction = getFragmentManager().beginTransaction(); fragment = SupportMapFragment.newInstance(options); transaction.replace(R.id.map, fragment).commit(); getFragmentManager().executePendingTransactions(); } if (fragment != null) GoogleMap map = fragment.getMap(); 

Además de ser más rápido para comenzar, no se mostrará un mapa mundial primero y una cámara se moverá en segundo lugar. El mapa comenzará con la ubicación especificada directamente.

Otro enfoque sería algo así como (suponiendo que su vista más alta es un FrameLayout llamado rootContainer , aunque funcionará siempre que siempre elija su contenedor superior sin importar qué tipo o nombre tenga):

 ((FrameLayout)findViewById(R.id.rootContainer)).getViewTreeObserver() .addOnGlobalLayoutListener(new OnGlobalLayoutListener() { public void onGlobalLayout() { layoutDone = true; } }); 

Modificar las funciones de la cámara para que solo funcione si layoutDone es true resolverá todos sus problemas sin tener que agregar funciones adicionales o cablear la lógica al controlador layoutListener .

Encontré que esto funciona y soy más simple que las otras soluciones:

 private void moveMapToBounds(final CameraUpdate update) { try { if (movedMap) { // Move map smoothly from the current position. map.animateCamera(update); } else { // Move the map immediately to the starting position. map.moveCamera(update); movedMap = true; } } catch (IllegalStateException e) { // Map may not be laid out yet. getWindow().getDecorView().post(new Runnable() { @Override public void run() { moveMapToBounds(update); } }); } } 

Esto intenta la llamada nuevamente después de que se haya ejecutado el diseño. Probablemente podría incluir una seguridad para evitar un ciclo infinito.

¿Por qué no usar algo como esto?

 CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(builder.build(), padding); try { map.moveCamera(cameraUpdate); } catch (Exception e) { int width = getResources().getDisplayMetrics().widthPixels; int height = getResources().getDisplayMetrics().heightPixels; cameraUpdate = CameraUpdateFactory.newLatLngBounds(builder.build(), width, height, padding); map.moveCamera(cameraUpdate); }