我已将GroundOverlay添加到地图并希望限制此区域内的滚动和缩放。
如何在 android 谷歌地图的某些范围内限制滚动?
是否可以从 MapFragment 立即获得运动点?
请帮我。
作为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);
您可以在文档中找到详尽的解释:Restricting the user's panning to a given area和GitHub 中的示例活动。
可能为时已晚,但这是我的解决方案:
禁用内置 GoogleMap 的手势。
添加了自定义手势(用于滚动、滑动和缩放)。
处理事件时检查允许的区域。
使用标准地图的功能手动设置边界/缩放。
这是我的例子:
[更新]
有一个问题 - 在地图初始化之前收到触摸事件。
检查 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,并检查当前位置是否在允许的范围内)。
太糟糕了,谷歌不允许我们拦截和阻止用户,我发现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);
}
然后控制过度滚动(和过度缩放)的处理程序每 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);
}
希望你会发现它有用!
onCameraChange
您可以尝试使用旧的轮询技术,而不是使用新的闪亮推送技术:map.getCameraPosition()
或map.getProjection().getVisibleRegion()
. 然后,您可以检查返回的值是否是您喜欢的,如果不是,map.moveCamera(...)
.
基本上你需要一个Handler
,它将获取相机位置的值,handleMessage
并且你每隔 10ms 左右向这个处理程序发送消息。里面handleMessage
做sendEmptyMessageDelayed
。
您也可以使用Runnable
代替Message
(但这是一个口味问题)。
限制缩放,您可以使用此代码
private GoogleMap mMap;
// Set a preference for minimum and maximum zoom.
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(14.0f);
在地图 API v2 中,GoogleMap 类上有一个 Min/MaxZoomLevel,但我不知道您是否可以以任何方式设置它。
另一种方法是添加一个
GoogleMap.OnCameraChangeListener
到您的地图并通过实施
public void onCameraChange(CameraPosition cameraPosition);
要限制可见区域,请使用 GoogleMap.moveCamera(cameraPosition)
也就是说,如果您希望用户能够滚动或缩放“一些”。