偏离MapFragment的中心,以获得animation,同时移动目标经纬度和缩放比例

我有一个具有透明View的MapFragment的UI。 地图占用整个屏幕,而视图只是屏幕的左三分之一。 结果,地图的默认“中心”closures。 当有人点击标记时,我想将标记居中在MapFragment的完全可见区域(而不是MapFragment本身的中心)。

由于这很难用文字来形容,所以让我用几张照片。 假设这是我的用户界面的外观:

默认地图

当用户点击一个标记时,我想要将它放在中间放大以便更近一点。 没有任何调整,这是你会得到:

以错误居中的标记为准的地图

我想要的是标记居中,但在右边的空间中,像这样:

正确居中标记的地图

如果不使用地图的投影来改变缩放级别,那么实现这个壮举非常容易:

// Offset the target latitude/longitude by preset x/y ints LatLng target = new LatLng(latitude, longitude); Projection projection = getMap().getProjection(); Point screenLocation = projection.toScreenLocation(target); screenLocation.x += offsetX; screenLocation.y += offsetY; LatLng offsetTarget = projection.fromScreenLocation(screenLocation); // Animate to the calculated lat/lng getMap().animateCamera(CameraUpdateFactory.newLatLng(offsetTarget)); 

但是,如果您同时更改缩放级别,则上述计算不起作用(因为在不同的缩放级别下lat / lng偏移会发生变化)。

让我列出一个尝试修复的列表:

  • 快速改变缩放级别,进行计算,放大到原来的相机位置,然后animation。 不幸的是,突然的相机改变(即使只是一秒钟)不幸的是非常明显,我想避免闪烁。

  • 将两个MapFragments叠加在一起,让其中一个执行计算,另一个显示。 我发现MapFragments并不是真正构build在彼此之上(这条路线上有不可避免的错误)。

  • 根据缩放级别的差异修改屏幕位置的x / y。 从理论上讲,这应该可以工作,但是它总是closures很多(〜.1纬度/经度,这足以让路)。

即使在缩放级别改变的情况下,是否有办法计算offsetTarget?

播放服务库的最新版本为GoogleMap添加了setPadding()函数。 这个填充将把所有的相机运动投影到偏移地图中心。 本文档进一步描述了地图填充的行为。

这里最初提出的问题现在可以简单地通过添加等于左侧布局的宽度的左侧填充来解决。

 mMap.setPadding(leftPx, topPx, rightPx, bottomPx); 

我find了一个真正适合这个问题的解决scheme。 解决方法是根据原始偏移量计算偏移量的中心,然后为地图的相机设置animation。 为此,首先将地图的相机移动到所需的缩放比例,计算该缩放比例的偏移量,然后恢复原始的缩放比例。 在计算新的中心之后,我们可以用CameraUpdateFactory.newLatLngZoom制作animation。

我希望这有帮助!

 private void animateLatLngZoom(LatLng latlng, int reqZoom, int offsetX, int offsetY) { // Save current zoom float originalZoom = mMap.getCameraPosition().zoom; // Move temporarily camera zoom mMap.moveCamera(CameraUpdateFactory.zoomTo(reqZoom)); Point pointInScreen = mMap.getProjection().toScreenLocation(latlng); Point newPoint = new Point(); newPoint.x = pointInScreen.x + offsetX; newPoint.y = pointInScreen.y + offsetY; LatLng newCenterLatLng = mMap.getProjection().fromScreenLocation(newPoint); // Restore original zoom mMap.moveCamera(CameraUpdateFactory.zoomTo(originalZoom)); // Animate a camera with new latlng center and required zoom. mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(newCenterLatLng, reqZoom)); } 

编辑:下面的代码是针对已弃用的v1地图。 这个SO回答说明了如何在V2中执行类似的操作: 如何获取谷歌地图V2中的纬度/经度跨度

你需要的关键方法应该在长/经度和百分比,而不是像素。 现在,地图视图将围绕地图中心缩放,然后移动到“更新”位于曝光区域的引脚。 你可以得到放大后的宽度,在一些屏幕偏差因素,然后重新映射。 下面是按下'+'之前的下面的代码的示例屏幕截图:之前 在这里输入图像说明在这里输入图像说明 主要代码:

 @Override public void onZoom(boolean zoomIn) { // TODO Auto-generated method stub controller.setZoom(zoomIn ? map.getZoomLevel()+1 : map.getZoomLevel()-1); int bias = (int) (map.getLatitudeSpan()* 1.0/3.0); // a fraction of your choosing map.getLongitudeSpan(); controller.animateTo(new GeoPoint(yourLocation.getLatitudeE6(),yourLocation.getLongitudeE6()-bias)); } 

所有代码:

  package com.example.testmapview; import com.google.android.maps.GeoPoint; import com.google.android.maps.ItemizedOverlay; import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView; import android.os.Bundle; import android.app.Activity; import android.graphics.drawable.Drawable; import android.view.Menu; import android.widget.ZoomButtonsController; import android.widget.ZoomButtonsController.OnZoomListener; public class MainActivity extends MapActivity { MapView map; GeoPoint yourLocation; MapController controller; ZoomButtonsController zoomButtons; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); map=(MapView)findViewById(R.id.mapview); map.setBuiltInZoomControls(true); controller = map.getController(); zoomButtons = map.getZoomButtonsController(); zoomButtons.setOnZoomListener(new OnZoomListener(){ @Override public void onVisibilityChanged(boolean arg0) { // TODO Auto-generated method stub } @Override public void onZoom(boolean zoomIn) { // TODO Auto-generated method stub controller.setZoom(zoomIn ? map.getZoomLevel()+1 : map.getZoomLevel()-1); int bias = (int) (map.getLatitudeSpan()* 1.0/3.0); // a fraction of your choosing map.getLongitudeSpan(); controller.animateTo(new GeoPoint(yourLocation.getLatitudeE6(),yourLocation.getLongitudeE6()-bias)); } }); //Dropping a pin, setting center Drawable marker=getResources().getDrawable(android.R.drawable.star_big_on); MyItemizedOverlay myItemizedOverlay = new MyItemizedOverlay(marker); map.getOverlays().add(myItemizedOverlay); yourLocation = new GeoPoint(0, 0); controller.setCenter(yourLocation); myItemizedOverlay.addItem(yourLocation, "myPoint1", "myPoint1"); controller.setZoom(5); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; } @Override protected boolean isRouteDisplayed() { // TODO Auto-generated method stub return false; } } 

和一个基本的布局:

 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testmapview" android:versionCode="1" android:versionName="1.0" > <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="YOURKEYHERE:)"/> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16" /> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <uses-library android:name="com.google.android.maps"/> <activity android:name="com.example.testmapview.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 

如果这不能完全回答你的问题,请告诉我,因为这是一个有趣的实验。

API的投影内容并不是那么有用,我遇到了同样的问题,并且推出了我自己的。

 public class SphericalMercatorProjection { public static PointD latLngToWorldXY(LatLng latLng, double zoomLevel) { final double worldWidthPixels = toWorldWidthPixels(zoomLevel); final double x = latLng.longitude / 360 + .5; final double siny = Math.sin(Math.toRadians(latLng.latitude)); final double y = 0.5 * Math.log((1 + siny) / (1 - siny)) / -(2 * Math.PI) + .5; return new PointD(x * worldWidthPixels, y * worldWidthPixels); } public static LatLng worldXYToLatLng(PointD point, double zoomLevel) { final double worldWidthPixels = toWorldWidthPixels(zoomLevel); final double x = point.x / worldWidthPixels - 0.5; final double lng = x * 360; double y = .5 - (point.y / worldWidthPixels); final double lat = 90 - Math.toDegrees(Math.atan(Math.exp(-y * 2 * Math.PI)) * 2); return new LatLng(lat, lng); } private static double toWorldWidthPixels(double zoomLevel) { return 256 * Math.pow(2, zoomLevel); } } 

使用这个代码,你可以围绕一个新的目标(即不是中心)转换地图。 我select使用一个定位点系统,其中(0.5,0.5)是默认的,代表屏幕的中间。 (0,0)是左上angular,(1,1)是左下angular。

 private LatLng getOffsetLocation(LatLng location, double zoom) { Point size = getSize(); PointD anchorOffset = new PointD(size.x * (0.5 - anchor.x), size.y * (0.5 - anchor.y)); PointD screenCenterWorldXY = SphericalMercatorProjection.latLngToWorldXY(location, zoom); PointD newScreenCenterWorldXY = new PointD(screenCenterWorldXY.x + anchorOffset.x, screenCenterWorldXY.y + anchorOffset.y); newScreenCenterWorldXY.rotate(screenCenterWorldXY, cameraPosition.bearing); return SphericalMercatorProjection.worldXYToLatLng(newScreenCenterWorldXY, zoom); } 

基本上,您使用投影来获得世界XY坐标,然后抵消该点并将其重新转换为LatLng。 然后你可以把它传递给你的地图。 PointD是简单的types,它包含x,y作为双精度,并且也是旋转的。

 public class PointD { public double x; public double y; public PointD(double x, double y) { this.x = x; this.y = y; } public void rotate(PointD origin, float angleDeg) { double rotationRadians = Math.toRadians(angleDeg); this.x -= origin.x; this.y -= origin.y; double rotatedX = this.x * Math.cos(rotationRadians) - this.y * Math.sin(rotationRadians); double rotatedY = this.x * Math.sin(rotationRadians) + this.y * Math.cos(rotationRadians); this.x = rotatedX; this.y = rotatedY; this.x += origin.x; this.y += origin.y; } } 

请注意,如果您要同时更新地图方位和位置,请务必在getOffsetLocation中使用新的方位。

简单的方法是链接相机animation。 首先,使用带有3个参数的animateCamera正常缩放到图片#2 ,并在CancellableCallback.onFinish进行投影计算,并使用代码更改中心的animation效果。

我相信(没有testing),这将看起来非常好,如果你设置animation持续时间两个animation的好值。 也许第二个animation应该比第一个快得多。 另外,如果在没有select标记的情况下,透明叠加的“视图”不可见,则在第二个animation期间,一个不错的UX将从左侧开始对其进行animation处理。

困难的方法是实施自己的球面墨卡托投影像一个地图API v2内部使用。 如果你只需要经度偏移,那就不应该那么辛苦。 如果您的用户可以改变方位,并且在缩放时不想将其重置为0,则也需要纬度偏移,这可能会稍微复杂一些。

我也认为,如果您有Maps API v2function,可以获得任何CameraPosition的投影,而不仅仅是当前的,那么您可能需要在此处提交function请求: http : //code.google.com/p/gmaps- api-issues / issues / list?can = 2&q = apitype = Android2 。 你可以很容易地在你的代码中使用它来达到你想要的效果。

为了设置一个标记,我们在Android中使用覆盖,在覆盖类==>上绘制方法放置这个东西

boundCenter(标记);

并在构造函数中也提到这样的:

  public xxxxxxOverlay(Drawable marker, ImageView dragImage, MapView map) { super(boundCenter(marker)); . . . . . } 

我几乎和你一样的问题(只有我的抵消是垂直),我已经尝试了一个解决scheme没有按预期工作,但可以帮助你(我们)find一个更好的解决scheme。

我所做的就是移动地图的相机来获得目标投影,并在那里应用偏移量。

我把代码放在一个Utils类中,它看起来像这样:

 public static CameraBuilder moveUsingProjection(GoogleMap map, CameraBuilder target, int verticalMapOffset) { CameraPosition oldPosition = map.getCameraPosition(); map.moveCamera(target.build(map)); CameraBuilder result = CameraBuilder.builder() .target(moveUsingProjection(map.getProjection(), map.getCameraPosition().target, verticalMapOffset)) .zoom(map.getCameraPosition().zoom).tilt(map.getCameraPosition().tilt); map.moveCamera(CameraUpdateFactory.newCameraPosition(oldPosition)); return result; } public static LatLng moveUsingProjection(Projection projection, LatLng latLng, int verticalMapOffset) { Point screenPoint = projection.toScreenLocation(latLng); screenPoint.y -= verticalMapOffset; return projection.fromScreenLocation(screenPoint); } 

然后,您可以使用由这些方法返回的CameraBuilder进行animation,而不是原始的(不是分离的)目标。

它工作(或多或less),但它有两个大问题:

  • 地图有时闪烁(这是最大的问题,因为它使黑客无法使用)
  • 这是一种黑客行为,可能在今天工作,明天可能无法工作(例如,我知道这个相同的algorithm在iOS上工作的事实)。

所以我没有去做这个,但是让我想到了另外一个select:如果你有一个完全相同的地图,但是隐藏了,你可以使用这个地图进行中间计算,然后在可见地图上使用最终的结果。

我认为这个select是可行的,但是这太可怕了,因为您需要为您和设备提供两张相同的地图,附带的开销。

正如我所说,我没有一个明确的解决scheme,但也许这有所帮助。

我希望这有帮助!

我已经通过使用地图上两个固定点的LatLng值计算距离向量来解决这个问题,然后使用标记位置,这个向量和一个预先定义的比率值创build另一个LatLng。 这里有很多对象,但是它不依赖于当前的缩放级别和地图的旋转,而且可能对你很有用!

 // Just a helper function to obtain the device's display size in px Point displaySize = UIUtil.getDisplaySize(getActivity().getWindowManager().getDefaultDisplay()); // The two fixed points: Bottom end of the map & Top end of the map, // both centered horizontally (because I want to offset the camera vertically... // if you want to offset it to the right, for instance, use the left & right ends) Point up = new Point(displaySize.x / 2, 0); Point down = new Point(displaySize.x / 2, displaySize.y); // Convert to LatLng using the GoogleMap object LatLng latlngUp = googleMap.getProjection().fromScreenLocation(up); LatLng latlngDown = googleMap.getProjection().fromScreenLocation(down); // Create a vector between the "screen point LatLng" objects double[] vector = new double[] {latlngUp.latitude - latlngDown.latitude, latlngUp.longitude - latlngDown.longitude}; // Calculate the offset position LatLng latlngMarker = marker.getPosition(); double offsetRatio = 1.0/2.5; LatLng latlngOffset = new LatLng(latlngMarker.latitude + (offsetRatio * vector[0]), latlngMarker.longitude + (offsetRatio * vector[1])); // Move the camera to the desired position CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(latlngOffset); googleMap.animateCamera(cameraUpdate); 

我正在处理相同的任务,并以类似的解决scheme作为最后一个选项结束:

根据缩放级别的差异修改屏幕位置的x / y。 从理论上讲,这应该可以工作,但是它总是closures很多(〜.1纬度/经度,这足以让路)。

但是,不是划分2<<zoomDifference的偏移量,我将偏移量计算为经度和纬度值之间的差异。 他们是double的,所以它给我足够的准确度。 所以尝试将您的偏移量转换为经纬度,而不是将您的位置转换为像素,只需执行相同的计算即可。


编辑

最后,我设法以另一种方式解决它(我相信最合适的一个)。 Map API v2有填充参数 。 只要设置它使摄像头采取MapView所有不隐藏的部分。 然后,相机将在animateCamera()之后reflection填充区域的中心