我最终采用了Romain Guy的建议(与我在问题中发布的相同)。也就是说,我通过扩展RelativeLayout
创建了自己的自定义ViewGroup
,然后增加了我的mapView
的大小以覆盖整个屏幕。扩展RelativeLayout ViewGroup
是必要的,这样我就可以重写dispatchDraw(...)
、onMeasure(...)
以及dispatchTouchEvent(...)
函数,以启用所需的地图旋转功能。
dispatchDraw(...)
函数本质上拦截对onDraw(...)
函数的调用,在该函数的输入上执行一些特定的操作,然后释放它以进行处理。在我们的情况下,我们将希望在实际onDraw(...)
函数处理之前旋转mapView
画布。这就是为什么我们需要重写此函数的原因。
具体来说,dispatchDraw(...)
函数以一个画布对象作为输入,该对象(在本例中)表示OSM mapView
对象(如下面的XML文件中所定义)。如果要对画布进行旋转,我们将希望找到地图的中心,将地图平移(即移动),使地图的中心位于坐标系的原点上,将地图绕坐标系的原点旋转,最后,我们将希望将此修改后的画布分派到渲染管道中的下一个阶段。
我的代码如下;请注意,Manager
是我自己创建的单例,除非您自己编写一个,否则在您的实现中不会存在!
@Override
protected void dispatchDraw(final Canvas pCanvas) {
final long startMs = System.currentTimeMillis();
if (Manager.getInstance().getMapRotationMode() != Constants.DISABLED) {
mBearing = Manager.getInstance().getPhoneBearing();
pCanvas.save(Canvas.MATRIX_SAVE_FLAG);
int canvasOffsetX = -(getWidth() / 2) + (screenWidth / 2);
int canvasOffsetY = -(getHeight() / 2) + (screenHeight / 2);
pCanvas.translate(canvasOffsetX, canvasOffsetY);
pCanvas.rotate(-mBearing, getWidth() / 2, getHeight() / 2);
super.dispatchDraw(pCanvas);
pCanvas.restore();
}
else {
super.dispatchDraw(pCanvas);
}
final long endMs = System.currentTimeMillis();
if (LOG_ENABLED) {
Log.i(TAG, "mapView Dispatch Time: " + (endMs - startMs) + "ms");
}
}
接下来,我们需要覆盖dispatchTouchEvent(...)方法,因为OSM mapView画布的任何旋转都会导致与该Activity相关的所有其他内容也一起旋转(这是由于我的具体实现而产生的副作用);也就是说,在被旋转后,触摸事件坐标仍然相对于mapView画布而不是相对于实际的手机。例如,如果我们想象画布被旋转了180度,那么如果用户试图将地图向左滑动,它实际上会向右移动,因为一切都是倒过来的!
代码中,你可以按照以下方式纠正这个问题:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
float degrees = Manager.getInstance().getPhoneBearing();
int numPointers = event.getPointerCount();
int[] pointerIDs = new int[numPointers];
PointerCoords[] pointerCoords = new PointerCoords[numPointers];
for (int i = 0; i < numPointers; i++) {
pointerIDs[i] = event.getPointerId(i);
pointerCoords[i] = new PointerCoords();
event.getPointerCoords(i, pointerCoords[i]);
}
for (int i = 0; i < numPointers; i++) {
float x = pointerCoords[i].x;
float y = pointerCoords[i].y;
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
float rad = (float) ((degrees * Math.PI) / 180f);
float s = (float) Math.sin(rad);
float c = (float) Math.cos(rad);
x -= centerX;
y -= centerY;
float tmpX = x * c - y * s;
float tmpY = x * s + y * c;
x = tmpX;
y = tmpY;
x += (600 - (screenWidth / 2)) * c - (600 - (screenHeight / 2)) * s;
y += (600 - (screenWidth / 2)) * s + (600 - (screenHeight / 2)) * c;
x += centerX;
y += centerY;
pointerCoords[i].x = x;
pointerCoords[i].y = y;
if (LOG_ENABLED) Log.i(TAG, "(" + x + ", " + y + ")");
}
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());
return super.dispatchTouchEvent(newEvent);
}
最后,使地图活动的对应XML正常工作的技巧是将
FrameLayout
用作布局中所有其他GUI元素的父元素。这使我能够使
mapView
的尺寸比我的Nexus One上的显示尺寸(480 x 800)大得多。这个解决方案还允许我在
FrameLayout
中嵌套一个
RelativeLayout
,同时在使用
match_parent
和类似参数时仍然尊重设备的实际显示尺寸。
我的XML布局的相关部分如下所示:
<?xml version="1.0" encoding="utf-8"?>
<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"/>
</RelativeLayout>
</FrameLayout>
我想强调这个解决方案并不是最好的解决方案,但对于我的概念验证应用程序来说,它运行良好!