使用CameraUpdateFactory.newLatLngBounds崩溃的moveCamera

时间:2012-12-03 22:04:23

标签: android android-mapview google-maps-android-api-2

我正在使用新的Android Google Maps API

我创建了一个包含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.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)

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

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

20 个答案:

答案 0 :(得分:132)

您可以在 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);
    }
});

答案 1 :(得分:54)

那些答案很好,但我会采用不同的方法,更简单的方法。 如果该方法仅在布局图之后才有效,请等待它:

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

答案 2 :(得分:49)

好的,我把它解决了。作为documented here,API无法在布局前使用。

使用的正确API描述为:

  

注意:只使用更简单的方法newLatLngBounds(boundary,padding)   如果它将用于移动,则生成一个CameraUpdate   相机在地图上经历了布局。在布局期间,API   计算所需的地图的显示边界   正确投影边界框。相比之下,你可以使用   CameraUpdate由更复杂的方法返回   newLatLngBounds(边界,宽度,高度,填充)随时都可以   在地图经历布局之前,因为API会计算出   显示您传递的参数的边界。

为了解决这个问题,我计算了我的屏幕尺寸并提供了宽度和高度

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

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

答案 3 :(得分:34)

解决方案比那简单......

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

答案 4 :(得分:14)

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

执行初始相机更新的最佳位置可能是使用Google's sample code中所示的一次性OnGlobalLayoutListener,例如请参阅 MarkerDemoActivity.java 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));
        }
    });
}

答案 5 :(得分:13)

这是我的修复,只要等到该情况下加载地图。

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

答案 6 :(得分:10)

接受的答案对我不起作用,因为我必须在我的应用程序的不同位置使用相同的代码。

我没有等待相机改变,而是根据Lee建议的指定地图大小创建了一个简单的解决方案。这是因为你的地图是屏幕的大小。

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

希望它可以帮助别人!

答案 7 :(得分:8)

正如评论中指出的那样,接受的答案是有点hacky。实施后,我注意到分析中有IllegalStateException个以上的{1}}日志。我使用的解决方案是添加OnMapLoadedCallback,其中执行CameraUpdate。回调已添加到GoogleMap。地图完全加载后,将执行相机更新。

这会导致地图在执行相机更新之前简要显示缩小(0,0)视图。我觉得这比造成崩溃或依赖无证件行为更容易接受。

答案 8 :(得分:5)

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

使用它可以为我工作

答案 9 :(得分:4)

在极少数情况下,MapView布局已完成,但GoogleMap布局尚未完成,ViewTreeObserver.OnGlobalLayoutListener本身无法阻止崩溃。我看到了Google Play服务包版本5084032的崩溃。这种罕见的情况可能是由于MapView可见性的动态变化引起的。

要解决此问题,我在GoogleMap.OnMapLoadedCallback中嵌入了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));
                    }
                });
            }
        }
    });
}

答案 10 :(得分:4)

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

答案 11 :(得分:3)

我已经创建了一种方法将两个回调:onMapReady和onGlobalLayout组合成一个单独的observable,只有在两个事件都被触发时才会发出。

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

答案 12 :(得分:2)

好的我面临同样的问题。我有我的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
这样做是第一次捕获异常(当地图“获取大小”作为一种说法...)然后,其他时候我需要重置相机,地图已经有大小并通过填充来实现。< / p>

问题在documentation中解释,它说:

  

在地图出现之前,请勿使用此相机更新更换相机   经历布局(为了使这种方法正确地确定   适当的边界框和缩放级别,地图必须有一个大小)。   否则将抛出IllegalStateException。它不是   足以使地图可用(即getMap()返回a   非空对象);包含地图的视图也必须经历过   布局使其尺寸已确定。如果你不能   确保发生这种情况,请改用newLatLngBounds(LatLngBounds, int, int, int)并手动提供地图的尺寸​​。

我认为这是一个相当不错的解决方案。 希望它可以帮到某人。

答案 13 :(得分:1)

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

在布局中使用它:

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

并替换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();

除了开始更快之外,将不会首先显示世界地图,而第二个显示相机。地图将直接从指定位置开始。

答案 14 :(得分:0)

另一种方法就是这样(假设你的最顶层视图是一个名为rootContainer的FrameLayout,即使只要你总是选择最顶层的容器,无论它有哪种类型或名称,它都会起作用:

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

修改您的相机功能仅在layoutDonetrue时才能解决所有问题,而无需添加额外的功能或将逻辑连接到layoutListener处理程序。

答案 15 :(得分:0)

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

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

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

答案 16 :(得分:0)

为什么不只使用这样的东西:

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

答案 17 :(得分:0)

OnCameraChangeListener() is deprecated中所述,setOnCameraChangeListener已被弃用。因此,您应该用以下三种方法之一替换它:

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

在我的情况下,我使用了OnCameraIdleListener,并且在里面删除了它,因为它在任何移动中都被反复调用。

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

更新

我删除了项目中的googleMap.setOnCameraIdleListener,因为有时在显示地图时并未调用它,但保留了googleMap.setOnCameraIdleListener(clusterManager)

答案 18 :(得分:0)

您可以利用Google Maps存储库中的一个帮助器类-在通知GoogleMap回调之前,它会等待布局和地图准备就绪。

原始资料在这里:

https://github.com/googlemaps/android-samples/blob/7ee737b8fd6d39c77f8b3716ba948e1ce3730e61/ApiDemos/java/app/src/main/java/com/example/mapdemo/OnMapAndViewReadyListener.java

也有Kotlin实现:

https://github.com/googlemaps/android-samples/blob/master/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt

public class OnMapAndViewReadyListener implements OnGlobalLayoutListener, OnMapReadyCallback {

/** A listener that needs to wait for both the GoogleMap and the View to be initialized. */
public interface OnGlobalLayoutAndMapReadyListener {
    void onMapReady(GoogleMap googleMap);
}

private final SupportMapFragment mapFragment;
private final View mapView;
private final OnGlobalLayoutAndMapReadyListener devCallback;

private boolean isViewReady;
private boolean isMapReady;
private GoogleMap googleMap;

public OnMapAndViewReadyListener(
        SupportMapFragment mapFragment, OnGlobalLayoutAndMapReadyListener devCallback) {
    this.mapFragment = mapFragment;
    mapView = mapFragment.getView();
    this.devCallback = devCallback;
    isViewReady = false;
    isMapReady = false;
    googleMap = null;

    registerListeners();
}

private void registerListeners() {
    // View layout.
    if ((mapView.getWidth() != 0) && (mapView.getHeight() != 0)) {
        // View has already completed layout.
        isViewReady = true;
    } else {
        // Map has not undergone layout, register a View observer.
        mapView.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    // GoogleMap. Note if the GoogleMap is already ready it will still fire the callback later.
    mapFragment.getMapAsync(this);
}

@Override
public void onMapReady(GoogleMap googleMap) {
    // NOTE: The GoogleMap API specifies the listener is removed just prior to invocation.
    this.googleMap = googleMap;
    isMapReady = true;
    fireCallbackIfReady();
}

@SuppressWarnings("deprecation")  // We use the new method when supported
@SuppressLint("NewApi")  // We check which build version we are using.
@Override
public void onGlobalLayout() {
    // Remove our listener.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
        mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    } else {
        mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
    isViewReady = true;
    fireCallbackIfReady();
}

private void fireCallbackIfReady() {
    if (isViewReady && isMapReady) {
        devCallback.onMapReady(googleMap);
    }
}
}

答案 19 :(得分:0)

尝试调用mMap.animateCamera时遇到相同的错误。我通过在 onMapReady 函数中调用 setOnMapLoadedCallback 回调解决了该问题,如下所示

public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

   // other codes go there

    mMap.setOnMapLoadedCallback(() -> {
            //Your code where exception occurs goes here...
            
        });
}

这应该工作正常。