为地图添加边界以避免在特定区域外滑动。

55

我的应用程序显示地图,我希望用户无法在特定区域滑动。因此,我尝试添加边界,但这会导致应用程序崩溃。以下是工作代码:

public class MapViewer extends Activity implements OnInfoWindowClickListener {
    private LatLng defaultLatLng = new LatLng(42.564241, 12.22759);
    private GoogleMap map;
    private int zoomLevel = 5;
    private Database db = new Database(this);

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mapviewer);

        try {
            map = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();
            if (map != null) {
                map.setMyLocationEnabled(true);
                map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
                map.getUiSettings().setRotateGesturesEnabled(false);

                map.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultLatLng, zoomLevel));

                this.addMerchantMarkers(new MarkerOptions());

                map.setOnInfoWindowClickListener(this);
            }
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onPause() {
        if (map != null) {
            map.setMyLocationEnabled(false);
            map.setTrafficEnabled(false);
        }
        super.onPause();
    }

    public void addMerchantMarkers(MarkerOptions mo) {
        SQLiteDatabase dbRead = db.getReadableDatabase();
        String[] columns = {"title", "addr", "lat", "lon"};
        Cursor result = dbRead.query("merchants", columns, null, null, null, null, null);

        while(result.moveToNext()) {
            String merchant = result.getString(0);
            String address = result.getString(1);
            float lat = result.getFloat(2);
            float lon = result.getFloat(3);

            LatLng pos = new LatLng(lat, lon);

            map.addMarker(mo.position(pos)
                    .title(merchant)
                    .snippet(address)
                    .icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_50)));;
        }
    }
}

这是我在onCreate方法中添加的代码,导致了崩溃:

    LatLngBounds.Builder builder = new LatLngBounds.Builder();
    builder.include(new LatLng(47.09194444, 18.52166666));
    builder.include(new LatLng(36.448311, 6.62555555));
    LatLngBounds bounds = builder.build();

    CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, 30);

    map.animateCamera(cu);

以下是 LogCat:

08-10 20:59:41.689: E/AndroidRuntime(6304): FATAL EXCEPTION: main
08-10 20:59:41.689: E/AndroidRuntime(6304): Process: com.example.myapp, PID: 6304
08-10 20:59:41.689: E/AndroidRuntime(6304): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.myapp/com.example.myapp.MapViewer}: java.lang.IllegalStateException: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view.  Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions.
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2215)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2264)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.access$800(ActivityThread.java:144)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1205)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.os.Handler.dispatchMessage(Handler.java:102)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.os.Looper.loop(Looper.java:136)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.main(ActivityThread.java:5139)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at java.lang.reflect.Method.invokeNative(Native Method)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at java.lang.reflect.Method.invoke(Method.java:515)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:796)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:612)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at dalvik.system.NativeStart.main(Native Method)
08-10 20:59:41.689: E/AndroidRuntime(6304): Caused by: java.lang.IllegalStateException: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view.  Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions.
08-10 20:59:41.689: E/AndroidRuntime(6304):     at mut.b(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at oxp.a(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at oxi.a(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at oyf.b(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at grl.onTransact(SourceFile:92)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.os.Binder.transact(Binder.java:361)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.google.android.gms.maps.internal.IGoogleMapDelegate$a$a.animateCamera(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.google.android.gms.maps.GoogleMap.animateCamera(Unknown Source)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at com.example.myapp.MapViewer.onCreate(MapViewer.java:59)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.Activity.performCreate(Activity.java:5231)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
08-10 20:59:41.689: E/AndroidRuntime(6304):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2169)
08-10 20:59:41.689: E/AndroidRuntime(6304):     ... 11 more

使用newLatLngBounds(LatLngBounds,int)时出错:地图大小不能为0。很可能,地图视图的布局尚未发生。要么等待布局完成,要么使用newLatLngBounds(LatLngBounds,int,int,int),该方法允许您指定地图的尺寸。看起来您的地图还没有准备好。还要检查Google Play服务的可用性。 - Raghunandan
我使用了newLatLngBounds(bounds, 30, 10, 10)方法,虽然没有崩溃,但是生成的地图并没有反映出我设置的边界。此外,我不知道如何使用这三个整数值:( - smartmouse
MapViewer.java 的第 59 行是什么? - Raghunandan
在第59行有 map.animateCamera(cu) 的代码。 - smartmouse
让我们在聊天中继续这个讨论 - Raghunandan
@smartmouse 根据您的问题或社区投票标记正确答案。 - ahmadalibaloch
10个回答

102

您可以使用MapLoadedCallBack;

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

而且你还可以使用在上面发生的这个事件。

  map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
        @Override
        public void onCameraChange(CameraPosition arg0) {
        map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
  }
});

1
即使我们将 moveCamera 放在 onMapLoaded 中,仍然不幸地出现了这个崩溃。我认为地图已经加载完成,但在某些设备上,可能没有完全布局。 因此,在 onMapLoaded 中放置 handler.post 对其有很大帮助。但是,尽管崩溃率降低了,我们仍然看到了崩溃的情况。 - tasomaniac
1
这对我有效,但我能看到地图加载在默认位置,稍有延迟后才移动到所需位置。使用addOnGlobalLayoutListener时,视觉效果要好得多。 - Singed

64

通过遵循日志中的建议,您可以防止错误发生:

“...使用newLatLngBounds(LatLngBounds,int,int,int),它允许您指定地图的尺寸。”

额外的参数是屏幕的宽度和高度。以下是您更新后的代码:

LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(new LatLng(47.09194444, 18.52166666));
builder.include(new LatLng(36.448311, 6.62555555));
LatLngBounds bounds = builder.build();

// begin new code:
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
int padding = (int) (width * 0.12); // offset from edges of the map 12% of screen

CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width, height, padding);
// end of new code

map.animateCamera(cu);

1
一直在寻找一个能够在Xamarin.Android上运行的解决方案,这个完美地解决了我的问题。此外,使用屏幕宽度的百分比来设置填充,以便无论我们针对哪种屏幕尺寸,填充都是相同的,这是对我之前代码的很大改进。非常感谢,Patrick! - YumeYume
1
这是我唯一可能的方法...所有其他解决方案在某些设备上都无法工作。 - Stephan Huewe
谢谢。在布局显示之前,animateCameramoveCamera是否可以无异常地工作? - CoolMind
问题是为什么你认为地图覆盖了整个屏幕!?它可能没有,对吧?int width = getResources().getDisplayMetrics().widthPixels; 这意味着您正在获取屏幕尺寸作为地图的布局,但是地图可能只覆盖屏幕的10%,并且可能与屏幕的右上角对齐,而不是中心,使用 int padding = (int) (width * 0.12); 可以设置12%的填充。 - Farid

7

onMapReady()的文档明确指出,您需要等待onMapReadyCallback和ViewTreeObserver.OnGlobalLayoutListener两个回调函数:

请注意,这并不能保证地图已经完成布局。因此,当回调方法被调用时,地图的大小可能还没有确定。如果您需要知道尺寸或调用需要知道尺寸的API方法,请获取地图的View并注册一个ViewTreeObserver.OnGlobalLayoutListener。

这有些不方便,但一种方法是使用RxJava。您可以在onMapReady中发出第一个observable,在onGlobalLayout中发出第二个observable,然后将它们合并在一起并执行所需操作。这样,您可以确保两个回调都已触发。


1
也许这个会有用?https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/OnMapAndViewReadyListener.java - Sebastian Gallese

4
Caused by: java.lang.IllegalStateException: Map size should not be 0. Most likely, layout has not yet occured for the map view.

导致此错误的原因是地图布局尚未完成,您应该在调用中实现OnMapReadyCallback,这将确保您的代码只在布局完成后运行或实现以下回调函数。

map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
    @Override
    public void onCameraChange(CameraPosition arg0) {
    map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
}
});

重复链接

使用CameraUpdateFactory.newLatLngBounds的moveCamera导致崩溃

IllegalStateException:地图大小不应为0

我不知道是否有一种方法可以合并这个链接。希望我的回答有所帮助。


你应该在调用中实现OnMapReadyCallback,这将确保你的代码只会在布局完成后运行或者实现下面的回调函数。请注意,这与布局是否准备好无关,它只是监听相机移动,包括用户交互。 - Farid

4
由于地图视图的布局尚未发生,导致使用newLatLngBounds(LatLngBounds, int)出错:地图大小不能为0。请等待布局完成或使用newLatLngBounds(LatLngBounds, int, int, int)指定地图的尺寸。错误信息:java.lang.IllegalStateException。参见文档https://developer.android.com/reference/com/google/android/gms/maps/CameraUpdateFactory.html#newLatLngBounds(com.google.android.gms.maps.model.LatLngBounds, int)。
在地图进行布局之前,请勿使用此摄像头更新更改摄像头(为了使此方法正确确定适当的边界框和缩放级别,地图必须具有大小)。否则,将抛出IllegalStateException异常。仅可用地图是不足够的(即getMap()返回非空对象);包含地图的视图还必须经过布局,以确定其尺寸。如果您无法确定是否已发生这种情况,请改用newLatLngBounds(LatLngBounds, int, int, int),并手动提供地图的尺寸。
注意:getMap()可能返回null。最好在初始化GoogleMap对象之前检查Google Play服务的可用性。

10
我用这段代码解决了崩溃的问题:http://pastebin.com/E5g2CAjS,但似乎填充值并没有影响任何东西。我仍然可以在地图上移动到其他国家... 它并没有阻止地图停留在某个特定区域。 - smartmouse
@smartmouse请阅读上面突出显示的部分,那是你最初的问题。 - Raghunandan
我可以使用 setOnMapLoadedCallback 处理这个吗? - smartmouse
@smartmouse 你负责什么? - Raghunandan
2
通过 setOnMapLoadedCallback 我检查地图是否已经完成布局。我错了吗? - smartmouse
你是正确的。而且你也可以使用一个先前的事件,即mMap.setOnCameraChangeListener(...)。 - ahmadalibaloch

4
这对我有效:
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    mMapFragment.getMapAsync(this);
}

@Override
public void onMapReady(GoogleMap googleMap) {
    mMapFragment.getView().getViewTreeObserver().addOnGlobalLayoutListener(this);
}

@Override
public void onGlobalLayout() {
    mMapFragment.getView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
    //Only after onMapReady & onGlobalLayout newLatLngBounds will be available
    initMapPosition(); //newLatLngBounds here
}

有时我会捕获到一个异常 com.google.maps.api.android.lib6.common.apiexception.c: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occured for the map view. Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions. - Vlad

3
我已经创建了一种方法来将两个回调函数:onMapReady和onGlobalLayout结合成一个单一的可观察对象,只有当这两个事件都被触发时才会发出信号。请参考以下链接:https://gist.github.com/abhaysood/e275b3d0937f297980d14b439a8e0d4a
public final class OnMapAndLayoutReady {

private OnMapAndLayoutReady() {
}

/**
 * Converts {@link OnMapReadyCallback} to an observable.
 * Note that this method calls {@link MapView#getMapAsync(OnMapReadyCallback)} so you there is no
 * need to initialize google map view manually.
 */
private static Observable<GoogleMap> loadMapObservable(final MapView mapView) {
    return Observable.create(new Observable.OnSubscribe<GoogleMap>() {
        @Override
        public void call(final Subscriber<? super GoogleMap> subscriber) {
            OnMapReadyCallback mapReadyCallback = new OnMapReadyCallback() {
                @Override
                public void onMapReady(GoogleMap googleMap) {
                    subscriber.onNext(googleMap);
                }
            };
            mapView.getMapAsync(mapReadyCallback);
        }
    });
}

/**
 * Converts {@link ViewTreeObserver.OnGlobalLayoutListener} to an observable.
 * This methods also takes care of removing the global layout listener from the view.
 */
private static Observable<MapView> globalLayoutObservable(final MapView view) {
    return Observable.create(new Observable.OnSubscribe<MapView>() {
        @Override
        public void call(final Subscriber<? super MapView> subscriber) {
            final ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    subscriber.onNext(view);
                }
            };
            view.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
        }
    });
}

/**
 * Takes {@link #globalLayoutObservable(MapView)} and {@link #loadMapObservable(MapView)} and zips their result.
 * This means that the subscriber will only be notified when both the observables have emitted.
 */
public static Observable<GoogleMap> onMapAndLayoutReadyObservable(final MapView mapView) {
    return Observable.zip(globalLayoutObservable(mapView), loadMapObservable(mapView), new Func2<MapView, GoogleMap, GoogleMap>() {
        @Override
        public GoogleMap call(MapView mapView, GoogleMap googleMap) {
            return googleMap;
        }
    });
}
}

2
将调用放在一个可运行的对象中,并将其发布到视图处理程序中,如下所示:
    mapFragment.getView().post(new Runnable() {
        @Override
        public void run() {
            map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), CAMERA_PADDING));
        }
    });

当视图布局完成后,这个可运行程序将会执行并正确地定位地图。ahmadalibalochs的答案存在问题,即在地图正确定位之前,您将看到地球仪的闪烁。将其发布到视图处理程序中可以解决此问题。

"mapFragment.getView().post"不是表示“当视图布局完成后,可运行项将执行”。它意味着Runnable内的任何内容都将在UI线程中执行,并不能保证视图已经完成布局。 - Farid

1
在Kotlin中,以下是我成功的做法。
以下代码有时会抛出异常:
override fun onMapReady(googleMap: GoogleMap) {
    mMap = googleMap
    //mMap.uiSettings.isZoomControlsEnabled = true
    mMap?.uiSettings?.isMyLocationButtonEnabled = true
    mMap?.isMyLocationEnabled = true
    val cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, padding)
    mMap?.animateCamera(cameraUpdate) 
}

使用newLatLngBounds(LatLngBounds, int)出错:地图大小不能为0。很可能,地图视图的布局尚未发生。请等待布局完成或使用newLatLngBounds(LatLngBounds, int, int, int),该函数允许您指定地图的尺寸。

下面的代码可避免此错误:

override fun onMapReady(googleMap: GoogleMap) {
    mMap = googleMap
    //mMap.uiSettings.isZoomControlsEnabled = true
    mMap?.uiSettings?.isMyLocationButtonEnabled = true
    mMap?.isMyLocationEnabled = true
    mMap?.setOnMapLoadedCallback {
        val cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, padding)
        mMap?.animateCamera(cameraUpdate)
    }
}

通过在运行我的CameraUpdate函数之前等待onMapLoadedCallback。

1
被标记为正确的答案并不是100%正确的。我认为在地图由于连接问题无法加载或地图更新过快而无法完全加载的情况下仍可能失败。 我已经使用了addOnGlobalLayoutListener。
ConstraintLayout mapLayout = (ConstraintLayout)findViewById(R.id.activityLayout);
mapLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
    @Override
    public void onGlobalLayout()
    {
         //Your map code
    }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接