android - setminzoompreference - moverCámara con CameraUpdateFactory.newLatLngBounds bloqueos
zoom google maps android api (16)
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 pila:
Caused by: java.lang.IllegalStateException:
Map size should not be 0. Most likely, layout has not yet
at maps.am.r.b(Unknown Source)
at maps.y.q.a(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?
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));
}
});
}
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
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 colapso con el paquete de servicios de Google Play versión 5084032. Este raro caso puede ser causado por el cambio dinámico 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));
}
});
}
}
});
}
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.
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));
}
});
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 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 devolución de llamada 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.
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 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.
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.
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 documentation , 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
unaIllegalStateException
. 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, utilicenewLatLngBounds(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.
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
.
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);
}
});
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:
<FrameLayout
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
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.
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 devolución de llamada 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
*/
}
map.moveCamera(CameraUpdateFactory.newLatLngZoom(bounds.getCenter(),10));
usar esto funcionó para mí