我正在开发一个应用程序,该应用程序使用OpenStreetMap(OSM)API在离线地图上显示由静态切片组成的各种兴趣点。
我目前正在实施的功能之一是让地图根据手机确定的方位旋转(通过GPS)。我能够毫不费力地实现实际旋转,但由于我的代码旋转了整个画布 - 也许是一种相当天真的方法 - 我现在屏幕上有空白的角落,没有新的瓷砖被加载以补偿事实旋转的瓷砖不再填充这些像素。
经过一段谷歌搜索后,我找到了一些关于如何解决这个问题的建议,但到目前为止还没有运气。
在Mr. Romain Guy's posts中的一个中,他提到了以下内容:
我过去做过这个,需要创建一个自定义 在dispatchDraw()方法中旋转Canvas的ViewGroup。您 还需要增加MapView的大小(以便它绘制得足够多) 旋转时的像素。)您还需要旋转触摸事件 dispatchTouchEvent()。或者如果你使用Android 3.0,你可以简单地打电话 theMapView.rotate()
根据地图轮换的建议,我已经按照建议实现了dispatchDraw()和dispatchTouchEvent(),但是我在提到需要增加MapView大小的部分时遇到了麻烦?
这是我在XML文件中做的事情,如this thread中建议的那样吗?
或者,我可以以某种方式覆盖处理我的地图旋转的子类RelativeLayout类中的onMeasure()函数吗?
欢迎提出建议和提示。
更新
为了找到可接受的解决方案,我尝试更改画布的大小。我们的想法是,如果画布尺寸大于实际屏幕尺寸,我可能会将空白角完全移出屏幕。不幸的是,似乎没有实际的canvas.size()选项;我找到的最好的是canvas.scale()。
使用canvas.scale(),我能够在水平和垂直尺寸上将画布的比例增加2倍。但这意味着图像被有效放大,导致地图图块出现不可接受的像素化。
有没有人知道画布的大小在哪里被声明,如果改变画布的大小可能实际上解决了我的问题?
答案 0 :(得分:4)
我最终跟随了Romain Guy的建议(我在问题中提出的建议)。也就是说,我通过扩展ViewGroup
创建了自己的自定义RelativeLayout
,然后我增加了mapView
的大小以覆盖整个屏幕。必须延长RelativeLayout ViewGroup
,以便我可以覆盖dispatchDraw(...)
,onMeasure(...)
以及dispatchTouchEvent(...)
函数,以便为我的应用程序启用所需的地图旋转功能。< / p>
dispatchDraw(...)
函数实质上拦截了对onDraw(...)
函数的调用,对该函数的输入执行了一些特定的操作,然后将其释放以进行处理。在我们的示例中,我们要在mapView
画布进入实际onDraw(...)
函数之前旋转它。这就是为什么我们需要覆盖这个功能。
具体来说,dispatchDraw(...)
函数将canvas对象作为输入,该对象(在本例中)表示OSM mapView
对象(如下面的XML文件中所定义)。如果要对画布应用旋转,我们将要定位地图的中心,平移(即移动)地图,使地图的中心位于坐标系的原点,旋转地图坐标系的原点,然后,最后,我们要将这个修改过的画布调度到渲染管道的下一个阶段。
我的代码如下;请注意Manager
是我自己的单例创建,除非您自己编写,否则在您的实现中不会存在!
/**
* @param pCanvas
* @return void
*
* This function intercepts all dispatches to the onDraw function of the
* super, and it rotates the canvas in accordance with the user's wishes
* using the phone bearing as determined either through the magnetometer
* or GPS fixes.
*/
@Override
protected void dispatchDraw(final Canvas pCanvas) {
final long startMs = System.currentTimeMillis();
// If automatic map rotation has been enabled, get bearing from phone:
if (Manager.getInstance().getMapRotationMode() != Constants.DISABLED) {
mBearing = Manager.getInstance().getPhoneBearing();
// Save the state of the transformation matrix:
pCanvas.save(Canvas.MATRIX_SAVE_FLAG);
// getWidth() and getHeight() return the size of the canvas as
// defined in the XML file, and not the size of the screen!
int canvasOffsetX = -(getWidth() / 2) + (screenWidth / 2);
int canvasOffsetY = -(getHeight() / 2) + (screenHeight / 2);
// Set origin of canvas to center of map:
pCanvas.translate(canvasOffsetX, canvasOffsetY);
// Rotate the canvas to the correct bearing:
pCanvas.rotate(-mBearing, getWidth() / 2, getHeight() / 2);
// Pass on the rotated canvas, and restore after that:
super.dispatchDraw(pCanvas);
// Balance out the call to save, and restore the matrix to
// saved state:
pCanvas.restore();
} // end if
else { // If map rotation has not been enabled:
super.dispatchDraw(pCanvas);
} // end else
final long endMs = System.currentTimeMillis();
if (LOG_ENABLED) {
Log.i(TAG, "mapView Dispatch Time: " + (endMs - startMs) + "ms");
} // end if
} // end dispatchDraw()
接下来我们需要覆盖dispatchTouchEvent(...)
,因为OSM mapView
画布的任何旋转最终不仅会旋转地图的图形表示,还会旋转与该活动相关的所有其他内容(这作为我具体实施的副作用而发生);也就是说,触摸事件坐标在旋转后相对于mapView
画布保持不变,而不是相对于实际手机。例如,如果我们想象画布旋转180度,那么如果用户尝试将地图向左平移,它将转移到右侧的地图,因为一切都是颠倒的!
在代码中,您可以按如下方式解决此问题:
/**
* @param event
* @return boolean
*
* This function intercepts all interactions with the touch display (that is,
* all touchEvents), and for each finger on the screen, or pointer, the
* function applies the necessary rotation to counter the rotation of the
* map. The coordinate of each pointer is also modified so that it returns
* the correct location on the enlarged canvas. This was necessary to return
* the correct coordinate for actions such as double-tap, and proper icon
* identification upon clicking an icon.
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// Get the number of pointers (i.e. fingers on screen) from the passed
// in MotionEvent:
float degrees = Manager.getInstance().getPhoneBearing();
int numPointers = event.getPointerCount();
int[] pointerIDs = new int[numPointers];
PointerCoords[] pointerCoords = new PointerCoords[numPointers];
// Extract all pointers from the touch event:
for (int i = 0; i < numPointers; i++) {
pointerIDs[i] = event.getPointerId(i);
pointerCoords[i] = new PointerCoords();
event.getPointerCoords(i, pointerCoords[i]);
} // end for
// Correct each pointer coordinate set for map rotation:
for (int i = 0; i < numPointers; i++) {
// x and y end up representing points on the canvas, although they
// are derived from points on the screen:
float x = pointerCoords[i].x;
float y = pointerCoords[i].y;
// Get the center of the MapView:
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
// Convert to radians
float rad = (float) ((degrees * Math.PI) / 180f);
float s = (float) Math.sin(rad);
float c = (float) Math.cos(rad);
// Translate point to origin:
x -= centerX;
y -= centerY;
// Apply rotation
float tmpX = x * c - y * s;
float tmpY = x * s + y * c;
x = tmpX;
y = tmpY;
// Offset the coordinates to compensate for the fact that the
// canvas is 1200 by 1200, the phone screen is smaller, and
// they don't overlap nicely:
x += (600 - (screenWidth / 2)) * c - (600 - (screenHeight / 2)) * s;
y += (600 - (screenWidth / 2)) * s + (600 - (screenHeight / 2)) * c;
// Translate point back:
x += centerX;
y += centerY;
pointerCoords[i].x = x;
pointerCoords[i].y = y;
// Catlog:
if (LOG_ENABLED) Log.i(TAG, "(" + x + ", " + y + ")");
} // end for
// Create new event to pass along the modified pointers.
// Need API level 9 or higher to make this work!
MotionEvent newEvent = MotionEvent.obtain(event.getDownTime(), event
.getEventTime(), event.getAction(), event.getPointerCount(),
pointerIDs, pointerCoords, event.getMetaState(), event
.getXPrecision(), event.getYPrecision(), event
.getDeviceId(), event.getEdgeFlags(),
event.getSource(), event.getFlags());
// Dispatch the newly modified touch event:
return super.dispatchTouchEvent(newEvent);
} // end dispatchTouchEvent()
最后,让地图活动的相应XML正常工作的技巧是使用FrameLayout
作为布局中所有其他GUI元素的父级。这使我的mapView
尺寸远大于我的Nexus One上的显示尺寸(480 x 800)。此解决方案还允许我在RelativeLayout
内嵌套FrameLayout
,同时在使用match_parent
和类似参数时仍然尊重设备的实际显示尺寸。
我的XML布局的相关部分如下所示:
<?xml version="1.0" encoding="utf-8"?>
<!--Note that the layout width and height is defined in px and not dip!-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/MapViewLayout">
<a.relevant.path.RotatingRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="1200px"
android:layout_height="1200px">
<org.osmdroid.views.MapView
android:id="@+id/mapview"
android:layout_width="1200px"
android:layout_height="1200px"
android:enabled="true"
android:clickable="true"/>
</a.relevant.path.RotatingRelativeLayout>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/leftSlideHandleButton"
android:layout_width="match_parent"
android:layout_height="60dip"
android:layout_centerHorizontal="true"
android:background="#D0000000">
<Button
android:id="@+id/mapZoomOutButton"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/zoom_out_button"
android:layout_alignParentLeft="true"
android:onClick="zoomOutButton"/>
<Button
android:id="@+id/mapZoomInButton"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/zoom_in_button"
android:layout_alignParentRight="true"
android:onClick="zoomInButton"/>
<TextView
android:id="@+id/headerSpeedText"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textColor="#33B5E5"
android:text="Speed: "
android:textSize="12sp"
android:paddingLeft="15dip"
android:layout_toRightOf="@id/mapZoomOutButton"/>
<TextView
android:id="@+id/headerSpeedReading"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textColor="#33B5E5"
android:text="N/A"
android:textSize="12sp"
android:paddingLeft="27dip"
android:layout_toRightOf="@id/headerSpeedText"/>
<TextView
android:id="@+id/headerBearingText"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textColor="#33B5E5"
android:text="Bearing: "
android:paddingLeft="15dip"
android:textSize="12sp"
android:layout_toRightOf="@id/mapZoomOutButton"
android:layout_below="@id/headerSpeedText"/>
<!-- Et Cetera... -->
</RelativeLayout>
</FrameLayout>
我想说的是,这个解决方案绝不是最好的解决方案,但它对于我的概念验证应用程序来说效果很好!