web-dev-qa-db-fra.com

moveCamera avec CameraUpdateFactory.newLatLngBounds se bloque

Je me sers de la nouvelle - Android Google Maps API

Je crée une activité qui inclut un MapFragment. Dans l'activité onResume, j'ai défini les marqueurs dans l'objet GoogleMap, puis j'ai défini un cadre de sélection pour la carte, qui comprend tous les marqueurs.

Ceci utilise le pseudo code suivant:

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

L'appel à map.moveCamera() provoque le blocage de mon application avec la pile suivante:

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 - au lieu de la méthode d'usine newLatLngBounds(), j'utilise la méthode newLatLngZoom(), le même piège ne se produit pas.

La onResume est-elle le meilleur endroit pour dessiner les marqueurs sur l'objet GoogleMap ou dois-je dessiner les marqueurs et définir la position de la caméra ailleurs?

89
Lee

OK j'ai travaillé ça. Comme documenté ici cette API ne peut pas être utilisée avant la mise en page.

L'API correcte à utiliser est décrite comme suit:

Remarque: Utilisez uniquement la méthode plus simple newLatLngBounds (borne, remplissage) générer une CameraUpdate si elle va être utilisée pour déplacer le fichier caméra après la mise en page de la carte. Lors de la mise en page, l'API calcule les limites d'affichage de la carte nécessaires pour projeter correctement le cadre de sélection. En comparaison, vous pouvez utiliser le CameraUpdate renvoyé par la méthode plus complexe newLatLngBounds (limite, largeur, hauteur, remplissage) à tout moment, même avant la mise en page de la carte, car l’API calcule le affiche les limites des arguments que vous passez.

Pour résoudre le problème, j’ai calculé la taille de mon écran et fourni la largeur et la hauteur 

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

Cela m’a ensuite permis de spécifier le pré-layout du cadre de sélection.

49
Lee

Vous pouvez utiliser la méthode simple newLatLngBounds dans OnCameraChangeListener . Tout fonctionnera parfaitement et vous n'avez pas besoin de calculer la taille de l'écran. Cet événement se produit après le calcul de la taille de la carte (si j'ai bien compris).

Exemple:

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);
    }
});
131
Paul Annekov

Ces réponses sont très bien, mais je choisirais une approche différente, plus simple . Si la méthode ne fonctionne que lorsque la carte est en place, attendez simplement:

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

La solution est plus simple que ça ....

// 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));
            }
        });
    }
}

Attrapez IllegalStateException et utilisez plutôt l'écouteur global sur la vue . Définir la taille de la limite à l'avance (avant la mise en page) à l'aide des autres méthodes signifie que vous devez calculer la taille de THE VIEW, pas celle du périphérique d'écran. Ils ne correspondent que si vous allez en plein écran avec votre carte et n'utilisez pas de fragments.

34
Daniele Segato

L'ajout et la suppression de marqueurs peuvent être terminés avant la mise en page, mais le déplacement de la caméra ne peut pas (sauf en utilisant newLatLngBounds(boundary, padding) comme indiqué dans la réponse du PO).

Le meilleur endroit pour effectuer une mise à jour initiale de l'appareil photo consiste probablement à utiliser une variable OnGlobalLayoutListener, comme indiqué dans l'exemple de code de Google , par exemple. voir l'extrait suivant de setUpMap() dans 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));
        }
    });
}
14
StephenT

C’est ma solution, attendez que la carte soit chargée dans ce cas. 

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));
        }
    });
}
11
pl3kn0r

La réponse acceptée ne fonctionnerait pas pour moi car je devais utiliser le même code à divers endroits de mon application. 

Au lieu d'attendre que la caméra change, j'ai créé une solution simple basée sur la suggestion de Lee de spécifier la taille de la carte. C'est dans le cas où votre carte est la taille de l'écran.

// 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));

J'espère que ça aide quelqu'un d'autre!

10
Teo Inke

La réponse acceptée est, comme indiqué dans les commentaires, un peu hacky. Après l’avoir implémenté, j’ai remarqué une quantité de journaux IllegalStateException dans les analyses supérieure à une valeur acceptable. La solution que j'ai utilisée consiste à ajouter une OnMapLoadedCallback dans laquelle la CameraUpdate est effectuée. Le rappel est ajouté à la GoogleMap. Une fois votre carte complètement chargée, la mise à jour de la caméra sera effectuée.

La carte affiche alors brièvement la vue zoom arrière (0,0) avant de mettre à jour la caméra. Je pense que cela est plus acceptable que de provoquer des plantages ou de s’en remettre à un comportement non documenté.

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

utiliser cela cela a fonctionné pour moi

5
Ramesh Bhupathi

Dans de très rares cas, la présentation MapView est terminée, mais celle de Google Map n'est pas terminée. ViewTreeObserver.OnGlobalLayoutListener lui-même ne peut pas arrêter le blocage. J'ai constaté le blocage avec la version 5084032 du package de services Google Play. Ce cas rare peut être causé par le changement dynamique de la visibilité de mon MapView.

Pour résoudre ce problème, j'ai incorporé GoogleMap.OnMapLoadedCallback dans 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));
                    }
                });
            }
        }
    });
}
4
Xingang Huang

J'ai utilisé une approche différente qui fonctionne pour les versions récentes du SDK Google Maps (9.6+) et basée sur onCameraIdleListener . Comme je l'ai vu jusqu'à présent, c'est la méthode de rappel onCameraIdle appelée toujours après onMapReady. Donc, mon approche ressemble à ce morceau de code (en considérant qu'il est mis dans 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
    */
}
4
Viacheslav

J'ai créé un moyen de combiner les deux callbacks: onMapReady et onGlobalLayout en une seule observable qui ne sera émise que lorsque les deux événements auront été déclenchés.

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

3
Abhay Sood

Ok je suis confronté au même problème. J'ai mon fragment avec mon tiroir SupportmapFragment, ABS et navigation. Ce que j'ai fait était:

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...
}

Et, au fait, j'appelle resetCamera() de onCreateView après avoir gonflé et avant de revenir.
Ce que cela fait est d’attraper l’exception une première fois (alors que la carte "prend une taille" comme moyen de le dire ...) puis, d’autres fois, je dois réinitialiser la caméra, la carte a déjà la taille et la fait rembourrage.

Le problème est expliqué dans documentation , il est écrit:

Ne changez pas la caméra avec cette mise à jour de la caméra tant que la carte n’a pas mise en page (pour que cette méthode détermine correctement le cadre de sélection et le niveau de zoom appropriés, la carte doit avoir une taille) . Sinon, une IllegalStateException sera lancée. Ce n'est pas suffisant pour que la carte soit disponible (c'est-à-dire que getMap() renvoie un objet non-null); la vue contenant la carte doit également avoir subi disposition telle que ses dimensions aient été déterminées. Si vous ne pouvez pas être Pour vous en assurer, utilisez plutôt newLatLngBounds(LatLngBounds, int, int, int) et fournissez les dimensions de la carte manuellement.

Je pense que c'est une solution assez décente… .. J'espère que cela aidera quelqu'un.

2
unmultimedio

Si vous devez attendre que d'éventuelles interactions utilisateur soient lancées, utilisez OnMapLoadedCallback comme décrit dans les réponses précédentes. Toutefois, si tout ce dont vous avez besoin est de fournir un emplacement par défaut pour la carte, aucune des solutions décrites dans ces réponses n’est nécessaire. MapFragmentet MapView peuvent tous deux accepter une GoogleMapOptions au début qui peut fournir l'emplacement par défaut parfaitement. La seule astuce consiste à ne pas les inclure directement dans votre mise en page car le système les appellera sans les options à ce moment-là, mais à les initialiser de manière dynamique.

Utilisez ceci dans votre mise en page:

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

et remplacez le fragment dans votre 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();

En plus d'être plus rapide au départ, aucune carte du monde ne sera affichée en premier et une caméra ne bougera qu'en second. La carte commencera directement avec l'emplacement spécifié.

1
Gábor

J'ai trouvé que cela fonctionne et est plus simple que les autres solutions:

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);
            }
        });
    }
}

Ceci réessaie l'appel après la mise en page. Pourrait probablement inclure une sécurité pour éviter une boucle infinie.

0
John Patterson

Pourquoi ne pas simplement utiliser quelque chose comme ça:

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);
}
0
Hristo Lalev

Comme indiqué dans OnCameraChangeListener () est obsolète , setOnCameraChangeListener est maintenant obsolète. Donc, vous devriez le remplacer par l’une des trois méthodes suivantes:

  • GoogleMap.OnCameraMoveStartedListener
  • GoogleMap.OnCameraMoveListener
  • GoogleMap.OnCameraIdleListener

Dans mon cas, j’ai utilisé OnCameraIdleListener et à l’intérieur je l’ai enlevé, car elle a été invoquée encore et encore pour tout mouvement.

googleMap.setOnCameraIdleListener {
    googleMap.setOnCameraIdleListener(null) // It removes the listener.
    googleMap.moveCamera(track)
    googleMap.cameraPosition
    clusterManager!!.cluster()
    // Add another listener to make ClusterManager correctly zoom clusters and markers.
    googleMap.setOnCameraIdleListener(clusterManager)
}

METTRE À JOUR

J'ai supprimé googleMap.setOnCameraIdleListener dans mon projet, car il n'était pas appelé parfois lorsqu'une carte était affichée, mais conservait googleMap.setOnCameraIdleListener(clusterManager).

0
CoolMind

Une autre approche pourrait ressembler à ceci (en supposant que votre vue la plus haute est un FrameLayout nommé rootContainer, même si cela fonctionne tant que vous choisissez toujours votre conteneur le plus haut, quel que soit son type ou son nom):

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

Si vous modifiez les fonctions de votre appareil photo pour ne fonctionner que si layoutDone est true, tous vos problèmes seront résolus sans qu'il soit nécessaire d'ajouter des fonctions supplémentaires ou de connecter une logique au gestionnaire layoutListener.

0
Machinarius