MoveCamera con CameraUpdateFactory.newLatLngBounds se bloquea

Estoy utilizando la nueva API de Google Maps para Android .

Creo una actividad que incluye un MapFragment. En la actividad onResume establezco los marcadores en el objeto GoogleMap y luego definimos 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 pila siguiente:

 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() método newLatLngZoom() , entonces no ocurre 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 algún otro lugar?

Puede utilizar el método newLatLngBounds simple 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 entiendo).

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 iría para un enfoque diferente, uno más simple. Si el método sólo funciona después de que el mapa se ha establecido, sólo hay que esperar:

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

De acuerdo, lo solucioné. Como se documenta aquí, la API no se puede usar antes del diseño.

El API correcto para usar se describe como:

Nota: Sólo utilice el método más simple newLatLngBounds (boundary, padding) para generar un CameraUpdate si va a ser utilizado para mover la cámara después de que el mapa haya sido modificado. Durante el diseño, la API calcula los límites de visualización del mapa que son necesarios para proyectar correctamente el cuadro delimitador. En comparación, puede utilizar el CameraUpdate devuelto por el método más complejo newLatLngBounds (límite, anchura, altura, relleno) en cualquier momento, incluso antes de que el mapa se haya sometido a diseño, ya que 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 siempre que el ancho y la altura de

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

Esto entonces me permitió especificar el cuadro de bounding pre-diseño.

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 utilice el oyente global en la vista. Ajustar el tamaño enlazado por adelantado (pre-diseño) usando los otros métodos significa que usted tiene que calcular el tamaño de LA VISTA, no el dispositivo de pantalla. Sólo coinciden si vas a pantalla completa con tu mapa y no usas fragmentos.

La adición y la eliminación de los marcadores se puede hacer antes de la finalización del diseño, pero el movimiento de la cámara no puede (excepto con 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 usar un OnGlobalLayoutListener un disparo como se muestra en el código de ejemplo de Google , por ejemplo, ver el siguiente fragmento 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)); } }); } 

La respuesta aceptada es, como se señaló en los comentarios, un poco hacky. Después de implementarlo, noté una cantidad de registros IllegalStateException superior a la aceptable en analytics. La solución que he utilizado es añadir un OnMapLoadedCallback en el que se CameraUpdate cabo CameraUpdate . La función de devolución de llamada se agrega a GoogleMap . Después de que su mapa se cargue completamente, se realizará la actualización de la cámara.

Esto hace que el mapa muestre brevemente la vista de zoom (0,0) antes de realizar la actualización de la cámara. Siento que esto es más aceptable que causar accidentes o confiar en comportamiento indocumentado.

Esta es mi solución, sólo espera hasta que el mapa se carga 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 tuve que usar el mismo código en varios lugares de mi aplicación.

En lugar de esperar a que la cámara cambie, he creado 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 sea del 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!

  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 bloqueo. Vi el bloqueo con la versión de paquete de servicios de Google Play 5084032. Este caso raro puede ser causado por el cambio dinámico de la visibilidad de mi MapView.

Para solucionar 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 las versiones recientes de Google Maps SDK (9.6+) y basado en onCameraIdleListener . Como veo hasta ahora es método de devolución de llamada onCameraIdle llamado siempre después onMapReady . Así que mi enfoque se parece a este pedazo de código (teniendo en cuenta que poner en la 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 mi SupportmapFragment, ABS, y el cajón de la 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 resetCamera() de onCreateView después de inflar y antes de regresar.
Lo que hace es capturar la excepción por primera vez (mientras que el mapa "obtiene un tamaño" como una forma de decirlo …) y luego, otras veces necesito reiniciar la cámara, el mapa ya tiene tamaño y lo hace a través de relleno.

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

No cambie la cámara con esta actualización de cámara hasta que el mapa se haya sometido a diseño (para que este método determine correctamente el cuadro delimitador y el nivel de zoom adecuados, 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 debe haberse sometido también a una disposición tal que se hayan determinado sus dimensiones. Si no puede estar seguro de que esto ha ocurrido, utilice newLatLngBounds(LatLngBounds, int, int, int) y proporcione las dimensiones del mapa manualmente.

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

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. El único truco es no incluirlos directamente en su diseño porque el sistema los llamará sin las opciones entonces pero para inicializarlos dinámicamente.

Utilícelo en su diseño:

 <FrameLayout android:id="@+id/map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> 

Y reemplazar 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 habrá mapa del mundo mostrado primero y una cámara se moverá en segundo lugar. El mapa comenzará directamente con la ubicación especificada.

Otra aproximación sería algo así como (Asumiendo que su vista superior es un FrameLayout llamado rootContainer , aunque funcione siempre que siempre elija su contenedor superior, sin importar el tipo o nombre que tenga):

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

La modificación de las funciones de la cámara sólo funciona si layoutDone es true resolverá todos sus problemas sin tener que agregar funciones adicionales o conectar la lógica al controlador layoutListener .

Encontré esto para trabajar y ser 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 de nuevo después de que el diseño se haya ejecutado. Probablemente podría incluir una seguridad para evitar un bucle infinito.

He creado una forma de combinar las dos devoluciones de llamada: onMapReady y onGlobalLayout en un único observable que emitirá sólo cuando ambos eventos hayan sido activados.

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

  • Cómo deshabilitar la función de rotación de mapas en el fragmento de mapa de Android
  • Obtener Lat Lang de un place_id devuelto por autocompletar lugar api
  • La API de Google Maps v2 no funciona
  • ¿Cómo comprobar si un InfoWindow de Google Maps todavía se muestra antes de actualizarlo?
  • Cómo convertir un LatLng y un radio a LatLngBounds en Android Google Maps API v2?
  • Cómo puedo dejar google maps api v2 ir directamente a mi ubicación
  • Google maps api v2 fuera de error de memoria
  • ¿Cómo agregar texto sobre polilínea en google maps?
  • Cómo mostrar mi ubicación en Google Maps para Android API v2
  • Cómo recuperar la ubicación del dispositivo actual y mostrarlo en el fragmento de mapa en un fragmento
  • Cómo agregar controles personalizados a MapFragment en Google Maps Android API v2?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.