moveCamera avec CameraUpdateFactory.newlatlngbounds se bloque

Je fais usage de la nouvelle API Android Google Maps .

Je crée une activité qui inclut un MapFragment. Dans l'activité onResume, j'ai défini les marqueurs dans L'objet GoogleMap, puis défini une boîte englobante pour la carte qui inclut tous les marqueurs.

Cela 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 crash 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)

If-au lieu de la méthode d'usine newLatLngBounds() j'utilise newLatLngZoom() méthode alors le même piège ne se produit pas.

Le onResume est-il le meilleur endroit pour dessiner les marqueurs sur L'objet GoogleMap ou devrais-je dessiner les marqueurs et définir la position de la caméra ailleurs?

88
demandé sur JJD 2012-12-04 02:04:22

17 réponses

Vous pouvez utiliser de simples newLatLngBounds méthode 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 je comprends bien).

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
répondu Paul Annekov 2012-12-10 11:35:36

Ces réponses sont très bien, mais j'opterais pour une approche différente, une approche plus simple. Si la méthode ne fonctionne qu'une fois la carte affichée, attendez-la:

map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
    @Override
    public void onMapLoaded() {
        map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
    }
});
54
répondu Leandro 2014-03-20 00:27:58

OK, je me suis débrouillé. 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:

Note: utilisez uniquement la méthode plus simple newLatLngBounds(boundary, padding) pour générer un CameraUpdate s'il va être utilisé pour déplacer le caméra après la carte a subi mise en page. Pendant la mise en page, L'API calcule les limites d'affichage de la carte qui sont nécessaires pour projeter correctement la boîte englobante. En comparaison, vous pouvez utiliser le CameraUpdate renvoyé par la méthode plus complexe newLatLngBounds (limite, largeur, hauteur, rembourrage) à tout moment, même avant que la carte ait subi la mise en page, car L'API calcule le afficher les limites à partir 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 la pré-mise en page de la boîte englobante.

49
répondu Lee 2014-10-06 12:39:54

La solution est plus simple que cela....

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

Catch {[1] } et utilisez l'écouteur global sur la vue à la place. Définir la taille liée à l'avance (pré-mise en page) en utilisant les autres méthodes signifie que vous devez calculer la taille de la vue, pas le périphérique d'écran. Ils ne correspondent que si vous allez en plein écran avec votre carte et ne pas utiliser des fragments.

34
répondu Daniele Segato 2014-10-06 12:31:18

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

Probablement le meilleur endroit pour effectuer une mise à jour initiale de la caméra est d'utiliser un one-shot OnGlobalLayoutListener comme indiqué dans 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
répondu StephenT 2014-10-06 12:16:29

C'est mon correctif, 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));
        }
    });
}
10
répondu pl3kn0r 2014-12-30 09:50:10

La réponse acceptée ne fonctionnerait pas pour moi parce que je devais utiliser le même code à différents 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!

9
répondu Teo Inke 2015-06-30 16:17:48

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é ci-dessus acceptable de journaux IllegalStateException dans analytics. La solution que j'ai utilisée est d'ajouter un OnMapLoadedCallback, dans lequel le CameraUpdate est effectuée. Le rappel est ajouté au GoogleMap. Une fois votre carte complètement chargée, la mise à jour de la caméra sera effectuée.

Cela fait que la carte affiche brièvement la vue agrandie (0,0) avant d'effectuer la mise à jour de la caméra. Je pense que c'est plus acceptable que provoquer des plantages ou s'appuyer sur un comportement non documenté.

8
répondu theblang 2014-02-19 17:38:38
 map.moveCamera(CameraUpdateFactory.newLatLngZoom(bounds.getCenter(),10));

Utilisez ceci cela a fonctionné pour mee

5
répondu Ramesh Bhupathi 2015-03-02 13:50:04

Dans de très rares cas, la mise en page MapView est terminée, mais la mise en page GoogleMap n'est pas terminée, ViewTreeObserver.OnGlobalLayoutListener lui-même ne peut pas arrêter le crash. J'ai vu le crash avec Google Play Services package Version 5084032. 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
répondu Xingang Huang 2014-10-06 12:19:24

J'ai utilisé une approche différente qui fonctionne pour les versions récentes de Google Maps SDK (9.6+) et basée sur onCameraIdleListener . Comme je le vois 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 (considérant qu'il est mis 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
    */
}
4
répondu Viacheslav 2016-11-30 08:39:23

Ok, je suis confronté au même problème. J'ai mon fragment avec mon SupportmapFragment, ABS et tiroir de 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 première fois (alors que la carte "obtient une taille" comme une façon de le dire...) et puis, d'autres fois, j'ai besoin de réinitialiser l'appareil photo, la carte a déjà la taille et le fait à travers le rembourrage.

Problème est expliqué dans documentation, il dit:

Ne changez pas la caméra avec cette mise à jour de la caméra tant que la carte n'a pas mise en page subie (pour que cette méthode détermine correctement l' zone de délimitation appropriée et niveau de zoom, la carte doit avoir une taille). Sinon, un IllegalStateException sera lancé. Il n'est PAS suffisante pour que la carte soit disponible (c'est-à-dire getMap() renvoie un objet non null); la vue contenant la carte doit également avoir subi disposition telle que ses dimensions ont été déterminées. Si vous ne pouvez pas être assurer que cela s'est produit, Utilisez newLatLngBounds(LatLngBounds, int, int, int) à la place et fournissez les dimensions de la carte manuellement.

Je pense que c'est une solution assez décente. Espérons que cela aide quelqu'un.

2
répondu unmultimedio 2014-10-06 13:43:58

J'ai créé un moyen de combiner les deux callbacks: onMapReady et onGlobalLayout en un seul observable qui n'émettra que lorsque les deux événements auront été déclenchés.

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

2
répondu Abhay Sood 2017-04-17 18:59:57

Si vous devez attendre que d'éventuelles interactions de l'utilisateur commencent, utilisez OnMapLoadedCallback comme décrit dans les réponses précédentes. Mais, 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. Les deux MapFragment et MapView peuvent accepter un GoogleMapOptions Au début qui peut fournir l'emplacement par défaut tout droit. La seule astuce est de ne pas les inclure directement dans votre mise en page car le système les appellera sans les options alors mais de les initialiser dynamiquement.

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 remplacer 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 à démarrer, il n'y aura pas de carte du monde affichée en premier et une caméra se déplace en second. La carte commencera par l'emplacement spécifié directement.

1
répondu Gábor 2014-12-18 10:27:43

Une autre approche serait quelque chose comme (en supposant que votre vue la plus haute est un FrameLayout nommé rootContainer, même si cela fonctionnera tant que vous choisissez toujours votre conteneur le plus haut quel que soit le type ou le nom qu'il a):

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

Modifier les fonctions de votre caméra pour ne fonctionner que si layoutDone est true résoudra tous vos problèmes sans avoir à ajouter des fonctions supplémentaires ou à connecter la logique au gestionnaire layoutListener.

0
répondu Machinarius 2014-10-06 12:17:56

J'ai trouvé que cela fonctionnait et était 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);
            }
        });
    }
}

Cela tente à nouveau l'appel après l'exécution de la mise en page. Pourrait probablement inclure une sécurité pour éviter une boucle infinie.

0
répondu John Patterson 2015-02-17 13:07:52

Pourquoi ne pas simplement utiliser quelque chose comme ceci:

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
répondu Hristo Lalev 2018-08-11 11:15:37