我結束了羅曼蓋伊的建議(和我在我的問題中發佈的建議一樣)。也就是說,我通過擴展RelativeLayout
創建了我自己的自定義ViewGroup
,然後我增加了我的mapView
的大小以提供整個屏幕的覆蓋範圍。擴展RelativeLayout ViewGroup
是必要的,以便我可以覆蓋dispatchDraw(...)
,onMeasure(...)
以及dispatchTouchEvent(...)
函數,以便爲我的應用程序啓用所需的地圖旋轉功能。
dispatchDraw(...)
函數實質上截獲對onDraw(...)
函數的調用,對該函數的輸入執行一些特定的操作,然後釋放它以進行處理。在我們的例子中,我們希望旋轉mapView
畫布,然後才能轉到實際的onDraw(...)
函數。這就是爲什麼我們需要重寫這個函數。
具體而言,dispatchDraw(...)
函數將用作輸入的畫布對象(在本例中)表示OSM mapView
對象(如下面的XML文件中所定義的)。如果要在畫布上應用旋轉,我們需要定位地圖的中心,平移(即移動)地圖,以便地圖的中心位於座標系的原點上,旋轉地圖座標系的原點,然後,最後,我們希望將這個修改後的畫布分配到渲染管線中的下一個階段。
我的代碼如下;請注意0是我自己的單例創建,除非您自己寫一個,否則在您的實現中不存在!
/**
* @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)。該解決方案還允許我在我的FrameLayout
中嵌套RelativeLayout
,同時在使用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>
我想此話,這種解決方案絕不是最佳的解決方案,但它制定了罰款爲我證明的 - 概念應用程序!
如果畫布旋轉到180度的倍數以外,您是否可以相對調度觸摸事件? 例如:50,90,270,300? – Mani
是的,任何任意旋轉角度都適用。我能夠從手機的磁力計讀取數據,然後使用這些讀數旋轉畫布,以便mapView的頂部始終顯示直接位於用戶面前的地標。希望是有道理的! –