在发布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上的示例活动。也许现在有点晚了,但这是我的解决方案:
禁用内置的Google地图手势。
添加自定义手势(用于滚动、快速移动和缩放)。
在处理事件时检查允许的区域。
使用标准地图函数手动设置边界/缩放。
这是我的一个示例:
[更新]
问题在于,在初始化地图之前接收到触摸事件。
在 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);
}
然后是控制超滚动(和超缩放)的 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);
}
希望您会觉得这非常有用!
OverscrollHandler
:
CameraUpdate update = CameraUpdateFactory.newLatLngBounds(BOUNDS, 20);
mMap.animateCamera(update, 2000, null);
- user3105453如果不想使用新的推送技术onCameraChange
,可以尝试使用旧的轮询技术:map.getCameraPosition()
或map.getProjection().getVisibleRegion()
。你可以检查返回值是否符合要求,如果不符合,可以使用map.moveCamera(...)
。
基本上,你需要一个Handler
来在handleMessage
中获取相机位置的值,并且每隔10毫秒发送消息给此处理程序。在handleMessage
中使用sendEmptyMessageDelayed
。
你也可以使用Runnable
代替Message
(但这是个人口味问题)。
限制缩放,您可以使用以下代码:
private GoogleMap mMap;
// Set a preference for minimum and maximum zoom.
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(14.0f);
GoogleMap.OnCameraChangeListener
public void onCameraChange(CameraPosition cameraPosition);
为了限制可见区域,可以使用GoogleMap.moveCamera(cameraPosition)。
如果您希望用户能够滚动或缩放“一些”,那么这个方法非常适用。
您还可以通过GoogleMapOptions完全禁用滚动/缩放事件。