Tôi đã kết thúc theo lời khuyên của Romain Guy (cùng một lời khuyên mà tôi đã đăng trong câu hỏi của tôi). Tức là, tôi đã tạo tùy chỉnh riêng của mình ViewGroup
bằng cách mở rộng RelativeLayout
và sau đó tăng kích thước của mapView
của tôi để cung cấp mức độ phù hợp cho toàn bộ màn hình.Việc mở rộng RelativeLayout ViewGroup
là cần thiết để tôi có thể ghi đè lên dispatchDraw(...)
, onMeasure(...)
, cũng như các chức năng dispatchTouchEvent(...)
để bật các tính năng xoay bản đồ mong muốn cho ứng dụng của tôi.
Chức năng dispatchDraw(...)
về cơ bản chặn cuộc gọi đến chức năng onDraw(...)
, thực hiện một số thao tác cụ thể trên đầu vào cho chức năng đó và sau đó giải phóng nó để được xử lý. Trong trường hợp của chúng tôi, chúng tôi sẽ muốn xoay canvas mapView
trước khi nó thực hiện theo cách của nó đến hàm onDraw(...)
thực tế. Đây là lý do tại sao chúng ta cần phải ghi đè hàm này.
Cụ thể, hàm dispatchDraw(...)
lấy làm đầu vào đối tượng canvas, (trong trường hợp này) đại diện cho đối tượng OSM mapView
(như được định nghĩa trong tệp XML bên dưới). Nếu xoay vòng được áp dụng cho canvas, chúng tôi sẽ muốn xác định vị trí trung tâm của bản đồ, dịch (tức là di chuyển) bản đồ sao cho trung tâm của bản đồ nằm trên nguồn gốc của hệ tọa độ, xoay bản đồ về nguồn gốc của hệ tọa độ, và sau đó, cuối cùng, chúng tôi sẽ muốn gửi canvas đã sửa đổi này đến giai đoạn tiếp theo trong đường dẫn hiển thị.
Mã của tôi cho điều này là dưới đây; lưu ý rằng Manager
là sáng tạo singleton của riêng tôi sẽ không tồn tại trong quá trình triển khai của bạn trừ khi bạn tự viết một bản!
/**
* @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()
Tiếp theo chúng ta sẽ cần phải ghi đè dispatchTouchEvent(...)
, bởi vì bất kỳ quay của OSM mapView
canvas kết thúc quay không chỉ đại diện đồ họa của bản đồ, mà còn mọi thứ khác liên quan đến Hoạt động đó (điều này xảy ra như một mặt ảnh hưởng của việc triển khai cụ thể của tôi); có nghĩa là, các tọa độ sự kiện chạm vẫn còn tương đối so với canvas mapView
sau khi được xoay và không liên quan đến điện thoại thực tế. Ví dụ: nếu chúng ta tưởng tượng canvas được xoay 180 độ, thì nếu người dùng cố gắng xoay bản đồ sang bên trái, thay vào đó nó sẽ di chuyển sang bản đồ ở bên phải, vì mọi thứ đều lộn ngược!
Trong mã, bạn có thể sửa chữa cho vấn đề này như sau:
/**
* @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()
Cuối cùng, lừa để nhận được XML tương ứng cho các hoạt động bản đồ để hoạt động đúng là sử dụng một FrameLayout
như phụ huynh cho tất cả khác Các phần tử GUI trong bố cục của tôi. Điều này cho phép tôi tạo kích thước mapView
lớn hơn đáng kể so với kích thước của màn hình trên Nexus One (480 x 800). Giải pháp này cũng cho phép tôi lồng một số RelativeLayout
vào trong số FrameLayout
trong khi vẫn tôn trọng kích thước hiển thị thực tế của thiết bị khi sử dụng match_parent
và các thông số tương tự.
Phần liên quan của layout XML của tôi trông như thế này:
<?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>
Tôi muốn nhận xét rằng giải pháp này là không phải là giải pháp tốt nhất, nhưng điều đó nó làm việc ra tốt đẹp cho tôi bằng chứng của -concept ứng dụng!
Bạn có thể gửi các sự kiện chạm tương đối không, nếu canvas được xoay sang mức độ khác với bội số của 180? Ví dụ: 50, 90, 270, 300? – Mani
Có, bất kỳ góc xoay tùy ý nào hoạt động. Tôi đã có thể đọc được từ từ kế của điện thoại, và sau đó sử dụng các chỉ số đó để xoay khung hình sao cho phía trên cùng của bản đồ luôn hiển thị mốc được đặt ngay trước mặt người dùng. Hy vọng rằng có ý nghĩa! –