限制Google Maps Android API v2中的滚动和缩放

17
我已将 GroundOverlay 添加到地图中,并希望限制此区域内的滚动和缩放。
如何在安卓谷歌地图上限制某些边界内的滚动?
是否可以立即从 MapFragment 获取运动点?
请帮帮我。
6个回答

16

在发布Google Play Services 9.4版本时,限制相机功能(终于!)被添加为一项新特性--你可以调用setLatLngBoundsForCameraTarget(LatLngBounds bounds)来设置允许移动的区域。

// Create a LatLngBounds that includes the city of Adelaide in Australia.
final LatLngBounds ADELAIDE = new LatLngBounds(
    new LatLng(-35.0, 138.58), new LatLng(-34.9, 138.61));

// Constrain the camera target to the Adelaide bounds.
mMap.setLatLngBoundsForCameraTarget(ADELAIDE);
您可以在文档中找到详细的说明:将用户的平移限制在给定区域内GitHub上的示例活动

13

也许现在有点晚了,但这是我的解决方案:

  1. 禁用内置的Google地图手势。

  2. 添加自定义手势(用于滚动、快速移动和缩放)。

  3. 在处理事件时检查允许的区域。

  4. 使用标准地图函数手动设置边界/缩放。

这是我的一个示例:

[更新]

问题在于,在初始化地图之前接收到触摸事件。

在 onInterceptTouchEvent 中检查空值。

此外,我发现我的解决方案比内置函数稍慢。

import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.VisibleRegion;

public class RestrictedMapView extends MapView {

    public static float MAX_ZOOM = 20;
    public static float MIN_ZOOM = 5;
    public static float MIN_ZOOM_FOR_FLING = 7;

    public static double MAX_LONGITUDE = 183.61;
    public static double MIN_LONGITUDE = 159.31;
    public static double MAX_LATITUDE = -32.98;
    public static double MIN_LATITUDE = -53.82;

    public static double DEF_LATITUDE = -41.78;
    public static double DEF_LONGITUDE = 173.02;
    public static float DEF_ZOOM = 7;

    private Handler mHandler = new Handler();
    private Context mContext;
    private VisibleRegion mLastCorrectRegion = null;
    private boolean mIsInAnimation = false;

    public RestrictedMapView(Context c, AttributeSet a, int o) {
        super(c, a, o);
        init(c);
    }
    public RestrictedMapView(Context c, AttributeSet a) {
        super(c, a);
        init(c);
    }
    public RestrictedMapView(Context c) {
        super(c);
        init(c);
    }

    public RestrictedMapView(Context c, GoogleMapOptions o) {
        super(c, o);
        init(c);
    }

    private GestureDetector mGestureDetector = null;
    private GestureDetector.SimpleOnGestureListener mGestudeListener =
            new GestureDetector.SimpleOnGestureListener() {

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (mIsInAnimation) return false;
            GoogleMap map = getMap();
            LatLng target = map.getCameraPosition().target;
            Point screenPoint = map.getProjection().toScreenLocation(target);
            Point newPoint = new Point(screenPoint.x + (int)distanceX, screenPoint.y + (int)distanceY);
            LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
            CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
                    mapNewTarget,map.getCameraPosition().zoom);         
            tryUpdateCamera(update, 0); 
            return true;
        }

        @Override
        public boolean onFling (MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (mIsInAnimation) return false;
            GoogleMap map = getMap();
            double zoom = map.getCameraPosition().zoom;
            if (zoom < MIN_ZOOM_FOR_FLING) 
                return false;
            int velocity = (int) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
            if (velocity < 500) return false;
            double k1 = 0.002d; /*exipemental*/
            double k2 = 0.002d;/*exipemental*/

            LatLng target = map.getCameraPosition().target;
            Point screenPoint = map.getProjection().toScreenLocation(target);
            Point newPoint = new Point(screenPoint.x - (int)(velocityX * k1 * zoom * zoom/*exipemental*/),
                    screenPoint.y - (int)(velocityY * k1 * zoom * zoom/*exipemental*/));
            LatLng mapNewTarget = map.getProjection().fromScreenLocation(newPoint);
            CameraUpdate update = CameraUpdateFactory.newLatLngZoom(
                    mapNewTarget,map.getCameraPosition().zoom); 
            tryUpdateCamera(update, (int)(velocity * k2 * zoom * zoom) /*exipemental*/);    
            return true;
        }
    };  
    private ScaleGestureDetector mScaleGestureDetector = null;
    private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestudeListener =
            new ScaleGestureDetector.SimpleOnScaleGestureListener() {

        @Override
        public boolean onScale (ScaleGestureDetector detector) {
            if (mIsInAnimation) return false;

            GoogleMap map = getMap();
            double zoom = map.getCameraPosition().zoom;

            double k = 1d / detector.getScaleFactor();
            int x = (int) detector.getFocusX();
            int y = (int) detector.getFocusY();
            LatLng mapFocus = map.getProjection().
                    fromScreenLocation(new Point(x, y));
            LatLng target = map.getCameraPosition().target;

            zoom = zoom + Math.log(detector.getScaleFactor()) / Math.log(2d);
            if (zoom < MIN_ZOOM)  
                if (zoom == MIN_ZOOM) return false;
                else zoom = MIN_ZOOM;
            if (zoom > MAX_ZOOM) 
                if (zoom == MAX_ZOOM) return false;
                else zoom = MAX_ZOOM;

            double dx = norm(mapFocus.longitude) - norm(target.longitude);
            double dy = mapFocus.latitude - target.latitude;
            double dk = 1d - 1d / k;
            LatLng newTarget = new LatLng(target.latitude - dy * dk, 
                    norm(target.longitude) - dx * dk);

            CameraUpdate update = CameraUpdateFactory.newLatLngZoom(newTarget, (float) zoom);           
            tryUpdateCamera(update, 0);         
            return true;
        }
    };


    private void tryUpdateCamera(CameraUpdate update, int animateTime) {
        GoogleMap map = getMap();
        final VisibleRegion reg = map.getProjection().getVisibleRegion();
        if (animateTime <= 0) {
            map.moveCamera(update);
            checkCurrentRegion(reg);
        } else {
            mIsInAnimation = true;
            map.animateCamera(update, animateTime, new GoogleMap.CancelableCallback() {
                @Override
                public void onFinish() {
                    mIsInAnimation = false;
                    checkCurrentRegion(reg);
                }
                @Override
                public void onCancel() {
                    mIsInAnimation = false;
                    checkCurrentRegion(reg);
                }
            });
        }
    }

    private void checkCurrentRegion(VisibleRegion oldReg) {
        GoogleMap map = getMap();
        VisibleRegion regNew = map.getProjection().getVisibleRegion();
        if (checkBounds(regNew)) {
            mLastCorrectRegion = regNew;
        } else {
            if (mLastCorrectRegion != null)
                oldReg = mLastCorrectRegion;
            mIsInAnimation = true;
            map.animateCamera(CameraUpdateFactory.newLatLngBounds(
                    oldReg.latLngBounds, 0),
                    200, new GoogleMap.CancelableCallback() {
                        @Override
                        public void onFinish() {
                            mIsInAnimation = false;
                        }                       
                        @Override
                        public void onCancel() {
                            mIsInAnimation = false;
                        }
                    });

        }
    }

    /**
     * 
     * 
     * @param lonVal
     * @return
     */
    private double norm(double lonVal) {
        while (lonVal > 360d) lonVal -= 360d;
        while (lonVal < -360d) lonVal += 360d;
        if (lonVal < 0) lonVal = 360d + lonVal;
        return lonVal;
    }

    private double denorm(double lonVal) {
        if (lonVal > 180d) lonVal = -360d + lonVal; 
        return lonVal;
    }

    private boolean checkBounds(VisibleRegion reg) {
        double left = Math.min(
                Math.min(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
                Math.min(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
        double right = Math.max(
                Math.max(norm(reg.farLeft.longitude), norm(reg.nearLeft.longitude)),
                Math.max(norm(reg.farRight.longitude), norm(reg.nearRight.longitude)));
        double top = Math.max( 
                Math.max(reg.farLeft.latitude, reg.nearLeft.latitude),
                Math.max(reg.farRight.latitude, reg.nearRight.latitude));
        double bottom = Math.min( 
                Math.min(reg.farLeft.latitude, reg.nearLeft.latitude),
                Math.min(reg.farRight.latitude, reg.nearRight.latitude));

        boolean limitBounds = left < MIN_LONGITUDE || right > MAX_LONGITUDE ||
                bottom < MIN_LATITUDE || top > MAX_LATITUDE;        
        return !limitBounds;
    }

    private void init(Context c) {
        try {
             MapsInitializer.initialize(c);
         } catch (GooglePlayServicesNotAvailableException e) {
             e.printStackTrace();
         }
        mContext = c;
        mHandler.post(new Runnable() {          
            @Override
            public void run() {
                GoogleMap map = getMap();
                if (map != null) {
                    getMap().getUiSettings().setZoomControlsEnabled(false);
                    map.getUiSettings().setAllGesturesEnabled(false);
                    map.moveCamera(CameraUpdateFactory.newLatLngZoom(
                            new LatLng(DEF_LATITUDE, DEF_LONGITUDE), DEF_ZOOM));
                    mLastCorrectRegion = map.getProjection().getVisibleRegion();
                    mGestureDetector = new GestureDetector(mContext, mGestudeListener);
                    mScaleGestureDetector = new ScaleGestureDetector(mContext, mScaleGestudeListener);
                } else mHandler.post(this);
            }
        });
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mGestureDetector != null) mGestureDetector.onTouchEvent(event);
        if (mScaleGestureDetector != null) mScaleGestureDetector.onTouchEvent(event);
        return super.onInterceptTouchEvent(event);
    }
}

在我的xml布局中定义片段:

<com.package....RestrictedMapView
    android:id="@+id/mapview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" /> 

在 XML 文件中,可以定义自定义的缩放/位置按钮,并设置单击侦听器以手动操作相机(在这种情况下,您必须检查 MAX_ZOOM 和 MIN_ZOOM,并检查当前位置是否在允许的范围内)。


1
你应该获得更多的声望。赞! - Vaibhav Jani
无法解决 getMap() 方法;因此目前无法进行测试。 - Tushar Gogna

5

很遗憾谷歌不允许我们拦截和阻止用户,我发现MaciejGórski的答案最符合我的需求。

我想与您分享我的解决方案(基于他的答案)。

首先我定义了边界和最大/最小缩放:

private final LatLngBounds BOUNDS = new LatLngBounds(new LatLng(41.8138,12.3891), new LatLng(41.9667, 12.5938));
private final int MAX_ZOOM = 18;
private final int MIN_ZOOM = 14;

我创建了这个小函数来测试当前相机边界是否超出最大边界,并返回纬度和经度的差异。

/**
 * Returns the correction for Lat and Lng if camera is trying to get outside of visible map
 * @param cameraBounds Current camera bounds
 * @return Latitude and Longitude corrections to get back into bounds.
 */
private LatLng getLatLngCorrection(LatLngBounds cameraBounds) {
    double latitude=0, longitude=0;
    if(cameraBounds.southwest.latitude < BOUNDS.southwest.latitude) {
        latitude = BOUNDS.southwest.latitude - cameraBounds.southwest.latitude;
    }
    if(cameraBounds.southwest.longitude < BOUNDS.southwest.longitude) {
        longitude = BOUNDS.southwest.longitude - cameraBounds.southwest.longitude;
    }
    if(cameraBounds.northeast.latitude > BOUNDS.northeast.latitude) {
        latitude = BOUNDS.northeast.latitude - cameraBounds.northeast.latitude;
    }
    if(cameraBounds.northeast.longitude > BOUNDS.northeast.longitude) {
        longitude = BOUNDS.northeast.longitude - cameraBounds.northeast.longitude;
    }
    return new LatLng(latitude, longitude);
}

然后是控制超滚动(和超缩放)的 Handler,每100毫秒限制一次。

/**
 * Bounds the user to the overlay.
 */
private class OverscrollHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        CameraPosition position = mMap.getCameraPosition();
        VisibleRegion region = mMap.getProjection().getVisibleRegion();
        float zoom = 0;
        if(position.zoom < MIN_ZOOM) zoom = MIN_ZOOM;
        if(position.zoom > MAX_ZOOM) zoom = MAX_ZOOM;
        LatLng correction = getLatLngCorrection(region.latLngBounds);
        if(zoom != 0 || correction.latitude != 0 || correction.longitude != 0) {
            zoom = (zoom==0)?position.zoom:zoom;
            double lat = position.target.latitude + correction.latitude;
            double lon = position.target.longitude + correction.longitude;
            CameraPosition newPosition = new CameraPosition(new LatLng(lat,lon), zoom, position.tilt, position.bearing);
            CameraUpdate update = CameraUpdateFactory.newCameraPosition(newPosition);
            mMap.moveCamera(update);
        }
        /* Recursively call handler every 100ms */
        sendEmptyMessageDelayed(0,100);
    }
}

这个处理程序必须作为当前类中的一个字段来定义(我是在扩展SupportMapFragment的类中进行此操作的)

private OverscrollHandler mOverscrollHandler = new OverscrollHandler();

最后,它必须在第一次调用时被调用,我在onActivityCreated的最后调用它以确保地图存在。
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mContext = getActivity();
    mMap = getMap();
    mMap.setMapType(GoogleMap.MAP_TYPE_NONE);
    mMap.addTileOverlay(new TileOverlayOptions().tileProvider(new VexLocalTileProvider(getResources().getAssets())));
    CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(41.87145, 12.52849), 14);
    mMap.moveCamera(upd);
    mOverscrollHandler.sendEmptyMessageDelayed(0,100);
}

希望您会觉得这非常有用!


同意,拥有一个更好的解决方案会很有趣,但不幸的是我找不到更好的一个。 - Luca Vitucci
认为唯一正确的做法是依赖于另一个库或等待更新,但是其他开源库使用起来就不那么顺手了 ;) - Warpzit
我使用了你的代码,但是用这两行简化了OverscrollHandlerCameraUpdate update = CameraUpdateFactory.newLatLngBounds(BOUNDS, 20); mMap.animateCamera(update, 2000, null); - user3105453

3

如果不想使用新的推送技术onCameraChange,可以尝试使用旧的轮询技术:map.getCameraPosition()map.getProjection().getVisibleRegion()。你可以检查返回值是否符合要求,如果不符合,可以使用map.moveCamera(...)

基本上,你需要一个Handler来在handleMessage中获取相机位置的值,并且每隔10毫秒发送消息给此处理程序。在handleMessage中使用sendEmptyMessageDelayed

你也可以使用Runnable代替Message(但这是个人口味问题)。


3

限制缩放,您可以使用以下代码:

private GoogleMap mMap;
// Set a preference for minimum and maximum zoom.
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(14.0f);

2
在地图API v2中,GoogleMap类有一个Min/MaxZoomLevel属性,但我不知道是否可以以任何方式设置它。
另一种方法是添加一个
GoogleMap.OnCameraChangeListener

将其添加到您的地图中并通过实施来完成。
public void onCameraChange(CameraPosition cameraPosition);

为了限制可见区域,可以使用GoogleMap.moveCamera(cameraPosition)。

如果您希望用户能够滚动或缩放“一些”,那么这个方法非常适用。

您还可以通过GoogleMapOptions完全禁用滚动/缩放事件。


5
问题在于onCameraChange方法只有在相机停止移动时才会被调用,因此我可以滑动区域并且只有在过度滑动后该方法才会被调用。 - user2090636

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