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(),则不会发生相同的陷阱。

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

4

20 回答 20

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);
    }
});
于 2012-12-10T11:30:27.627 回答
57

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

map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
    @Override
    public void onMapLoaded() {
        map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
    }
});
于 2014-03-20T00:27:58.220 回答
52

好的,我解决了这个问题。如此处所述,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)

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

于 2012-12-03T22:28:55.430 回答
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 的大小,而不是屏幕设备。只有当您全屏使用地图并且不使用片段时,它们才会匹配。

于 2013-01-28T09:51:59.733 回答
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));
        }
    });
}
于 2014-12-30T09:50:10.547 回答
15

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

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

希望它可以帮助别人!

于 2015-06-30T16:17:48.183 回答
14

添加和删​​除标记可以在布局完成前完成,但不能移动相机(除非newLatLngBounds(boundary, padding)按照 OP 的回答中所述使用)。

执行初始相机更新的最佳位置可能是使用Google 示例代码OnGlobalLayoutListener中所示的 one-shot,例如,请参阅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));
        }
    });
}
于 2013-01-26T00:40:42.333 回答
8

正如评论中指出的那样,公认的答案有点老套。实施后,我注意到IllegalStateException分析中的日志数量高于可接受的数量。我使用的解决方案是添加一个OnMapLoadedCallback在其中CameraUpdate执行的。回调被添加到GoogleMap. 地图完全加载后,将执行相机更新。

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

于 2014-02-19T17:38:38.813 回答
6
 map.moveCamera(CameraUpdateFactory.newLatLngZoom(bounds.getCenter(),10));

使用它对我有用

于 2015-02-07T07:30:05.963 回答
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));
                    }
                });
            }
        }
    });
}
于 2014-07-15T15:29:17.830 回答
4

我创建了一种方法来组合两个回调:onMapReady 和 onGlobalLayout 到一个单独的 observable 中,只有当这两个事件都被触发时才会发出。

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

于 2017-04-17T18:59:57.503 回答
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
    */
}
于 2016-11-30T08:39:23.077 回答
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()返回非空对象);包含地图的视图也必须经过布局,以便确定其尺寸。如果您不能确定是否发生了这种情况,newLatLngBounds(LatLngBounds, int, int, int)请改用并手动提供地图的尺寸​​。

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

于 2013-07-24T04:21:38.730 回答
1

如果您需要等待可能的用户交互开始,OnMapLoadedCallback请按照前面答案中的说明使用。但是,如果您只需要为地图提供默认位置,则不需要这些答案中概述的任何解决方案。两者都MapFragment可以MapView接受一个GoogleMapOptions可以提供默认位置的 at start。唯一的技巧是不要将它们直接包含在您的布局中,因为系统将在没有选项的情况下调用它们,而是动态初始化它们。

在您的布局中使用它:

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

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

于 2014-12-17T09:50:41.477 回答
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);
            }
        });
    }
}

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

于 2015-02-17T12:52:21.117 回答
0

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

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

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

于 2013-01-26T00:44:47.833 回答
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);
}
于 2018-08-11T11:15:37.210 回答
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...
            
        });
}

这应该可以正常工作。

于 2020-08-09T03:53:27.747 回答
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);
    }
}
}
于 2018-12-20T22:08:15.603 回答
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).

于 2018-12-19T12:12:27.077 回答