用CameraUpdateFactory.newLatLngBounds崩溃moveCamera

我正在使用新的Android Google Maps API 。

我创build了一个包含MapFragment的活动。 在活动onResume将标记设置到GoogleMap对象中,然后为包含所有标记的地图定义边界框。

这是使用下面的伪代码:

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

map.moveCamera()的调用导致我的应用程序崩溃,出现以下堆栈:

 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) 

如果 – 而不是newLatLngBounds()工厂方法,我使用newLatLngZoom()方法,那么相同的陷阱不会发生。

onResume是将标记绘制到GoogleMap对象上的最佳位置,还是应该在其他位置绘制标记并设置相机位置?

您可以在OnCameraChangeListener中使用简单的newLatLngBounds方法。 所有的工作都会很好,你不需要计算屏幕尺寸。 这个事件发生在地图大小计算之后(据我所知)。

例:

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

这些答案很好,但我会采取不同的方法,一个更简单的方法。 如果该方法只在地图铺设后才起作用,就等待:

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

好的,我解决了这个问题。 正如这里所logging的那样,API不能用于布局前。

使用正确的API描述为:

注意:只有使用更简单的方法newLatLngBounds(boundary,padding)才能在地图布局完成后用于移动摄像机,以生成CameraUpdate。 在布局过程中,API会计算正确投影边界框所需的地图显示边界。 相比之下,甚至在地图布局之前,您可以随时使用由更复杂的方法newLatLngBounds(边界,宽度,高度,填充)返回的CameraUpdate,因为API会根据您传递的参数计算显示边界。

要解决这个问题,我计算了我的屏幕大小,并提供了宽度和高度

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

这允许我指定边界框预布局。

解决scheme比这更简单….

 // 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 ,而是在视图上使用全局侦听器。 使用其他方法预先设置边界大小(预布局)意味着您必须计算THE VIEW的大小,而不是屏幕设备。 他们只匹配,如果你全屏与你的地图,不使用片段。

添加和删​​除标记可以在布局前完成,但移动摄像机不能(除了使用OP的答案中指出的newLatLngBounds(boundary, padding) )。

执行初始相机更新的最佳位置是使用一次性的OnGlobalLayoutListener ,如Google的示例代码所示 ,例如,请参阅setUpMap()的以下摘录:

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

这是我的修复,只是等待直到地图加载在这种情况下。

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

正如在评论中指出的那样,接受的答案是一个小黑客。 在实现它之后,我注意到分析中的IllegalStateException日志数量超过了可接受的数量。 我使用的解决scheme是添加一个执行OnMapLoadedCallbackCameraUpdate 。 callback被添加到GoogleMap 。 完成加载地图后,将执行相机更新。

这会导致地图在执行相机更新之前简要地显示缩小(0,0)视图。 我觉得这比碰撞或者依靠无证行为更可以接受。

接受的答案不适用于我,因为我不得不在我的应用程序的各个地方使用相同的代码。

我不是等待相机改变,而是根据Lee提出的指定地图尺寸的build议,创build了一个简单的解决scheme。 这是万一你的地图是屏幕的大小。

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

希望它能帮助别人!

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

使用它为我工作

在极less数情况下,MapView布局已经完成,但是GoogleMap布局还没有完成, ViewTreeObserver.OnGlobalLayoutListener本身也无法停止崩溃。 我看到了Google Play服务包版本5084032的崩溃。这种罕见的情况可能是由于我的MapView的可见性的dynamic变化造成的。

为了解决这个问题,我在onGlobalLayout()embedded了GoogleMap.OnMapLoadedCallback

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

我使用了不同的方法,适用于最新版本的Google Maps SDK(9.6+),并基于onCameraIdleListener 。 就像我目前看到的那样,在onMapReady之后总是调用onCameraIdle的callback方法。 所以我的方法看起来像这段代码(考虑到它放在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 */ } 

好吧,我面临同样的问题。 我有我的片段与我的SupportmapFragment,ABS和导航抽屉。 我做的是:

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

而且,顺便说一句,我打电话resetCamera()onCreateView膨胀和返回之前。
这样做是捕捉exception第一次(而地图“获取大小”作为一种说法…),然后,其他时间我需要重置相机,地图已经有大小,并通过填充。

问题在文档中解释,它说:

不要更换摄像头,直到地图经过布局(为了使此方法正确确定合适的边界框和缩放级别,地图必须具有大小)。 否则会抛出IllegalStateExceptionexception。 这是不够的地图是可用的(即getMap()返回一个非空的对象); 包含地图的视图也必须经过布局,以确定其尺寸。 如果您不能确定发生了这种情况,请newLatLngBounds(LatLngBounds, int, int, int)使用newLatLngBounds(LatLngBounds, int, int, int)并手动提供地图的尺寸​​。

我认为这是一个相当不错的解决scheme。 希望它可以帮助别人。

我已经创build了一种方法,将两个callback函数onMapReady和onGlobalLayout合并为一个单独的observable,只有当两个事件都被触发时才会发射。

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

如果您需要等待可能的用户交互开始,请使用OnMapLoadedCallback ,如前面的答案中所述。 但是,如果您只需要为地图提供默认位置,则不需要这些答案中列出的任何解决scheme。 MapFragmentMapView都可以在开始时接受GoogleMapOptions ,它可以提供默认的位置。 唯一的窍门是不要直接将它们包含在布局中,因为系统会在没有选项的情况下调用它们,而是dynamic初始化它们。

在你的布局中使用这个:

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

并replaceonCreateView()的片段:

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

除了更快的开始,没有先显示的世界地图,相机再次移动。 地图将直接从指定位置开始。

另一种方法是类似的(假设你的最顶层的视图是一个名为rootContainer的FrameLayout,尽pipe只要你总是select你的最上面的容器,不pipe它是哪种types或名称):

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

修改你的相机function,只有在layoutDonetrue才能解决你所有的问题,而不必增加额外的function,或者将逻辑连接到layoutListener处理程序。

我发现这个工作,比其他解决scheme更简单:

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

这在布局运行后再次尝试调用。 可能可能包括一个安全来避免无限循环。