带CameraUpdateFactory.newLatLngBounds的moveCamera崩溃


96

我正在使用新的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对象上的最佳位置,还是我应该绘制标记并将相机位置设置在其他位置?

Answers:


133

您可以在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
之所以可以这样做是因为setOnCameraChangeListener可以保证在布局完成后至少运行一次?这是未来的证明吗?
Glenn Bech 2013年

3
@SteelRat这就是为什么我反对使用该解决方案。您永远不会知道google何时更改此设置,即使它在所有android版本或设备上都可以正常运行。
Glenn Bech 2013年

1
@SteelRat我同意,但是在活动的生命周期中可能多次调用它?我只是在争辩说,即使在这种情况下可行,这也不是一个很好的解决方案。我认为OnGlobalLayoutListener / Treelistener是解决此问题的更正确方法。
Glenn Bech 2013年

4
也许添加map.moveCamera(CameraUpdateFactory.scrollBy(1,1)); 上面的代码之后会成功吗?

3
setOnCameraChangeListener现在已弃用
osrl 2013年

56

这些答案都很好,但是我会选择另一种方法,一种更简单的方法。如果该方法仅在布局地图后才起作用,请等待它:

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

1
经过大量的实验,我发现我需要实现这个答案和上面Daniele的结合。有时地图尚未加载,有时布局尚未完成。在这两种情况发生之前,我都无法根据需要移动相机。
RobP 2014年

2
docs中对此方法发出警告:“如果由于连接问题而导致地图永远无法加载,或者由于用户不断与地图互动而导致地图不断变化且无法完成加载,则不会触发此事件。” (强调我的)。
stkent 2014年

1
这延迟了比必要时间更长的时间,因为它会等待地图图块在错误的位置加载,然后再移动到正确的位置。
约翰·帕特森

1
在大多数情况下,射击需要3到10秒之间的时间,这是无法接受的。
Nima G

这是移动相机的地方。我建议使用map.animateCamera而不是map.moveCamera
Bharat Dodeja

51

好,我解决了。如此处所述,不能在布局前使用API​​。

正确使用的API描述为:

注意:如果要在地图经过布局后用于移动摄影机时,仅使用更简单的方法newLatLngBounds(boundary,padding)来生成CameraUpdate。在布局期间,API计算正确投影边界框所需的地图显示边界。相比之下,您可以随时使用更复杂的方法newLatLngBounds(boundary,width,height,padding)返回的CameraUpdate,即使在进行布局布局之前也是如此,因为API会根据您传递的参数来计算显示边界。

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

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

然后,这使我可以指定边框预布局。


18
但是,如果地图不占据整个屏幕怎么办?
theblang 2014年

就我而言,我只是假设屏幕比实际屏幕大,并且需要更仔细地计算填充,因此实际上并不太大。一个简单得多的原因。
rfay 2014年

2
您可以显示如何根据屏幕尺寸进行计算吗?
Daniel Gomez Rico

这不会崩溃,但是要使用的宽度和高度接近纯粹的猜测,要绘制的确切区域确实不可预测。
文斯

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

IllegalStateException在视图上捕获并使用全局侦听器。使用其他方法预先设置装订大小(预布局)意味着您必须计算THE VIEW的大小,而不是屏幕设备的大小。仅当您全屏浏览地图且不使用片段时,它们才匹配。


到目前为止,我一直在使用您的小片段,但仍然失败...正确的答案是第一个:-)
cesards's

1
如何/何时失败?从来没有这个问题。第一个答案只是将硬编码的大小传递给它作为后备。“工作正常”与“不再崩溃”中的工作一样,但是它没有做同样的事情。
Daniele Segato

@ andrea.spot我不记得为什么我说的更简单。我想与此同时接受的解决方案已经过编辑。在目前接受的解决方案假设您的地图把所有的屏幕,这并非总是如此。如果您不喜欢这种情况,则需要知道地图的大小,或者使用我的方法或类似方法。
Daniele Segato

还请检查下面的Xingang Huang的答案,这使它有所补充。
Daniele Segato 2015年

15

这是我的解决方法,请耐心等待直到在这种情况下加载地图。

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

简单高效+1
Shid中

14

可以在布局完成前完成添加和删除标记的操作,但是不能移动相机(除非newLatLngBounds(boundary, padding)按照OP的答案中的说明使用)。

可能执行初始相机更新的最佳场所是使用一次性OnGlobalLayoutListener如图谷歌的示例代码,例如,从看到下面的摘录setUpMap()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));
        }
    });
}


我在方向更改时收到此错误。我也从示例代码中找到了这个,但这对我不起作用。我仍然收到相同的错误。
osrl

14

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

我没有等待相机更改,而是根据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));

希望它能帮助别人!


8

正如评论中所指出的那样,公认的答案有点不客气。实施后,我注意到IllegalStateException分析中的日志数量超过了可接受的水平。我使用的解决方案是添加OnMapLoadedCallback在其中CameraUpdate执行的。回调已添加到中GoogleMap。地图完全加载后,将执行相机更新。

这确实会导致地图在执行相机更新之前短暂显示缩小的(0,0)视图。我认为这比导致崩溃或依靠未记录的行为更可接受。



4

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

为了解决这个问题,我的嵌入式GoogleMap.OnMapLoadedCallbackonGlobalLayout()

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

有趣的是:如果我将其重构mapView.getViewTreeObserver()局部变量,则会发生以下错误“ IllegalStateException:此ViewTreeObserver尚未激活,请再次调用getViewTreeObserver()”。你有尝试过吗?
JJD 2014年

如果mapView.getViewTreeObserver()。isAlive()恰好为假,我会为map.setOnMapLoadedCallback()再放一个备用广告
索纳顿2016年

4

我使用了基于onCameraIdleListener的不同方法,该方法适用于Google Maps SDK的最新版本(9.6+)。到目前为止,我看到的是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
    */
}


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充气后返回之前。
这样做是在第一次捕获异常时(地图“获得尺寸”来表达它的方式...),然后,其他时候我需要重置相机,地图已经具有尺寸并通过填充进行操作。

问题在文档中进行了说明,内容为:

在对地图进行布局之前,请勿使用此相机更新来更改相机(为了使此方法正确确定适当的边界框和缩放级别,地图必须具有尺寸)。否则IllegalStateException将抛出。映射不可用(即getMap()返回非null对象)是不够的;包含地图的视图也必须经过布局,以便确定其尺寸。如果不能确定发生这种情况,请newLatLngBounds(LatLngBounds, int, int, int)改用并手动提供地图的尺寸​​。

我认为这是一个不错的解决方案。希望它能帮助某人。


1
布局完全加载时,是否有任何监听器可以监听。?
Sagar Nayak

是在newLatLngBounds()或中引发异常animateCamera(),还是在两者中都引发异常?会不会导致新的例外?
CoolMind

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

除了启动速度更快之外,不会首先显示世界地图,其次是移动相机。地图将直接从指定位置开始。


0

另一种方法是这样的(假设最顶层的视图是一个名为的FrameLayout rootContainer,尽管只要您始终选择最顶层的容器,无论它具有哪种类型或名称,它都将起作用):

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

修改你的相机功能只工作,如果layoutDone就是true将解决所有的问题,而无需添加额外的功能或逻辑连线到layoutListener处理程序。


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

布局运行后,它将再次尝试调用。可能包括避免无限循环的安全措施。


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

为什么newLatLngBounds(builder.build(), padding)不在try-catch中?是否会在中发生此异常moveCamera()
CoolMind

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

0

OnCameraChangeListener()中所述,不推荐使用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)


感谢您的答复。我已经想出了几乎相同的解决方案。
Arslan Maqbool

@ArslanMaqbool,谢谢!我正在处理中,如果您分享您的变体,我们将非常感谢。
CoolMind

我的荣幸@CoolMind
Arslan Maqbool

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

这应该工作正常。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.