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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25231949/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 09:15:25  来源:igfitidea点击:

Add bounds to map to avoid swiping outside a certain region

androidgoogle-maps-android-api-2

提问by smartmouse

My app shows a map and i want that users can't swipe over a certain region. So i'm trying to add bounds but it makes the app to crash. Here is the working code:

我的应用程序显示了一张地图,我希望用户不能在某个区域上滑动。所以我试图添加边界,但它使应用程序崩溃。这是工作代码:

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

And this is the code i add in onCreate method that cause the crash:

这是我在 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);

Here is the LogCat:

这是 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.access0(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

回答by ahmadalibaloch

You can use MapLoadedCallBack;

您可以使用 MapLoadedCallBack;

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

and also you can use this event that occurs prier to above.

并且您也可以使用发生在上面的事件。

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

回答by Patrick C.

You can keep the error from occurring by following the advice from the log:

您可以按照日志中的建议避免错误发生:

"...use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions."

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

The extra parameters are the width and height of your screen. Here's how your updated code would look:

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

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

回答by ElDae

Documentation for onMapReady() clearly states that you have to wait for both onMapReadyCallback and ViewTreeObserver.OnGlobalLayoutListener:

onMapReady() 的文档明确指出您必须等待 onMapReadyCallback 和 ViewTreeObserver.OnGlobalLayoutListener:

Note that this does not guarantee that the map has undergone layout. Therefore, the map's size may not have been determined by the time the callback method is called. If you need to know the dimensions or call a method in the API that needs to know the dimensions, get the map's View and register an ViewTreeObserver.OnGlobalLayoutListener as well.

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

It is quite inconvenient, but one way of doing it would be by using RxJava. You could emit two observables, first one in onMapReady and second one in onGlobalLayout, then zip them together and do whatever you need to do. This way you can be certain that both callbacks were fired.

这很不方便,但一种方法是使用 RxJava。您可以发出两个 observable,第一个在 onMapReady 中,第二个在 onGlobalLayout 中,然后将它们压缩在一起并执行您需要执行的任何操作。这样你就可以确定两个回调都被触发了。

回答by Evgeny Nozdrev

This worked for me:

这对我有用:

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

回答by Bikash

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

The reason Behind this error is because the Map layout has not been completed, You should implement OnMapReadyCallback in your calls, which will ensure that your code will only run after your layout is completed or Implement the below callback

这个错误背后的原因是因为地图布局还没有完成,你应该在你的调用中实现 OnMapReadyCallback ,这将确保你的代码只有在你的布局完成后才会运行或者实现下面的回调

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

Duplicate Links

重复链接

moveCamera with CameraUpdateFactory.newLatLngBounds crashes

带有 CameraUpdateFactory.newLatLngBounds 的 moveCamera 崩溃

IllegalStateException map size should not be 0

IllegalStateException 地图大小不应为 0

I don't if there is a way to merge this link. I Hope i helped

如果有办法合并此链接,我不知道。我希望我有所帮助

回答by Raghunandan

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.

引起: java.lang.IllegalStateException: Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. 最有可能的是,地图视图的布局尚未发生。要么等到布局发生,要么使用 newLatLngBounds(LatLngBounds, int, int, int) 来指定地图的尺寸​​。

From the docs https://developer.android.com/reference/com/google/android/gms/maps/CameraUpdateFactory.html#newLatLngBounds(com.google.android.gms.maps.model.LatLngBounds, int)

从文档https://developer.android.com/reference/com/google/android/gms/maps/CameraUpdateFactory.html#newLatLngBounds(com.google.android.gms.maps.model.LatLngBounds, int)

Do not change the camera with this camera update until the map has undergone layout (in order for this method to correctly determine the appropriate bounding box and zoom level, the map must have a size). Otherwise an IllegalStateException will be thrown. It is NOT sufficient for the map to be available (i.e. getMap() returns a non-null object); the view containing the map must have also undergone layout such that its dimensions have been determined. If you cannot be sure that this has occured, use newLatLngBounds(LatLngBounds, int, int, int) instead and provide the dimensions of the map manually.

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

Note: getMap()could return null. It is better to check for Availability of Google play services before initialize GoogleMap object.

注意:getMap()可能返回空值。最好在初始化 GoogleMap 对象之前检查 Google Play 服务的可用性。

回答by Abhay Sood

I've created a way to combine the two callbacks: onMapReady and onGlobalLayout into one single observable which will emit only when both the events have been triggered.

我已经创建了一种方法来将两个回调:onMapReady 和 onGlobalLayout 组合成一个单一的可观察对象,它只会在两个事件都被触发时发出。

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

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

回答by McP

Place the call within a runnable and post it to the views handler, like so:

将调用放置在可运行对象中并将其发布到视图处理程序,如下所示:

    mapFragment.getView().post(new Runnable() {
        @Override
        public void run() {
            map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), CAMERA_PADDING));
        }
    });

When the view has been laid out the runnable will execute and correctly position the map. The issue with ahmadalibalochs answer, is that you will see a flash of the globe before it correctly positions itself. Posting it to the view handler, resolves this issue.

当视图布置好后,runnable 将执行并正确定位地图。ahmadalibalochs 答案的问题是,在地球正确定位之前,您会看到地球的闪光。将它发布到视图处理程序,解决了这个问题。

回答by Siddharth

The answer that is marked correct is not a 100% correct. I think that can still fail in cases where the map never loads due to connectivity, or if the map gets updated so quickly that it never fully finishes loading. I have used the addOnGlobalLayoutListener.

标记为正确的答案并非 100% 正确。我认为,在地图因连接而从未加载的情况下,或者如果地图更新得太快以至于从未完全完成加载,这仍然可能会失败。我使用了 addOnGlobalLayoutListener。

ConstraintLayout mapLayout = (ConstraintLayout)findViewById(R.id.activityLayout);
mapLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
    @Override
    public void onGlobalLayout()
    {
         //Your map code
    }
});