【问题标题】:limit scrolling and zooming Google Maps Android API v2限制滚动和缩放 Google Maps Android API v2
【发布时间】:2013-02-05 06:58:26
【问题描述】:

我已将GroundOverlay 添加到地图中,并希望限制此区域内的滚动和缩放。

如何在 android 谷歌地图的某些范围内限制滚动?

是否可以从 MapFragment 中即时获取运动点?

请帮帮我。

【问题讨论】:

    标签: android google-maps-android-api-2


    【解决方案1】:

    作为 Google Play Services 9.4 版本的一部分,已(终于!)添加了限制相机功能——您可以致电 setLatLngBoundsForCameraTarget(LatLngBounds bounds) 设置允许的平移区域。 p>

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

    您可以在文档中找到详尽的解释:Restricting the user's panning to a given areasample activity in GitHub

    【讨论】:

      【解决方案2】:

      可能为时已晚,但这是我的解决方案:

      1. 禁用内置 GoogleMap 的手势。

      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,并检查当前位置是否在允许的范围内)。

      【讨论】:

      • 你应该得到更多的声誉。赞一个。
      • 无法解析方法getMap();到目前为止无法测试
      【解决方案3】:

      可惜google不让我们拦截和屏蔽用户,我发现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);
      }
      

      然后是控制overscroll(和overzoom)的Handler,每100ms限制一次。

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

      希望你会发现它有用!

      【讨论】:

      • 同意,如果有更好的解决方案会很有趣,可惜我找不到更好的解决方案。
      • 认为唯一正确的做法是依赖另一个库或等待更新,可惜其他开源库不那么好用;)
      • 我使用你的代码,但用这两行简化了OverscrollHandlerCameraUpdate update = CameraUpdateFactory.newLatLngBounds(BOUNDS, 20);mMap.animateCamera(update, 2000, null);
      【解决方案4】:

      您可以尝试使用旧的轮询技术:map.getCameraPosition()map.getProjection().getVisibleRegion(),而不是使用新的闪亮推送技术 onCameraChange。然后你可以检查返回值是否是你喜欢的,如果不是,map.moveCamera(...)

      基本上你需要一个Handler,它将在handleMessage 中获取相机位置的值,并且你每隔10ms 左右向这个处理程序发送消息。在handleMessage 里面做sendEmptyMessageDelayed

      您也可以使用Runnable 代替Message(但这只是个人喜好问题)。

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          在地图 API v2 中,GoogleMap 类上有一个 Min/MaxZoomLevel,但我不知道您是否可以通过任何方式设置它。

          另一种方法是添加一个

          GoogleMap.OnCameraChangeListener
          

          到您的地图并通过实施

          public void onCameraChange(CameraPosition cameraPosition);
          

          要限制可见区域,使用 GoogleMap.moveCamera(cameraPosition)

          也就是说,如果您希望用户能够滚动或缩放“一些”。

          您也可以通过GoogleMapOptions 完全停用滚动/缩放事件

          【讨论】:

          • 问题是 onCameraChange 方法仅在相机运动停止时调用,所以我可以过度滚动该区域,然后该方法才会调用
          猜你喜欢
          • 2014-01-12
          • 1970-01-01
          • 2013-08-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-15
          • 1970-01-01
          相关资源
          最近更新 更多