2011-08-29 38 views
8

Tôi đang làm việc trên một ứng dụng sử dụng API OpenStreetMap (OSM) để hiển thị nhiều địa điểm ưa thích khác nhau trên bản đồ ngoại tuyến, bao gồm các ô tĩnh.Android: Nhận bản đồ OSM xoay để điền vào toàn bộ màn hình

Một trong những tính năng mà tôi hiện đang triển khai là xoay bản đồ theo vòng bi được xác định bởi điện thoại (qua GPS). Tôi đã có thể thực hiện phép quay thực sự mà không cần nỗ lực quá nhiều, nhưng vì mã của tôi xoay toàn bộ canvas - có lẽ là một cách tiếp cận khá ngây thơ - bây giờ tôi có các góc trống trên màn hình mà không có ô mới nào được tải để bù đắp cho thực tế rằng các ô được xoay không còn lấp đầy các pixel này nữa.

Sau một chút của Googling, tôi đã tìm thấy một số gợi ý về cách tôi có thể giải quyết vấn đề này, nhưng cho đến nay không có may mắn.

Trong một Mr. Romain Guy's posts, ông đề cập đến sau đây:

Tôi đã làm điều này trong quá khứ và nó đòi hỏi phải tạo ra một tùy chỉnh ViewGroup mà xoay Canvas trong dispatchDraw() phương pháp. Bạn cũng cần phải tăng kích thước của MapView (để thu hút đủ pixel khi xoay.) Bạn cũng sẽ cần xoay các sự kiện chạm trong dispatchTouchEvent(). Hoặc nếu bạn sử dụng Android 3.0 bạn chỉ có thể gọi theMapView.rotate()

Lấy lời khuyên của ông về luân chuyển bản đồ, tôi đã thực hiện dispatchDraw() và dispatchTouchEvent() như đề xuất, nhưng tôi đang gặp rắc rối với các một phần mà ông đề cập đến rằng tôi cần phải tăng kích thước của MapView?

Đây có phải là điều tôi làm trong tệp XML, như được đề xuất trong this thread không?

Hoặc, bằng cách nào đó tôi có thể ghi đè hàm onMeasure() trong lớp RelativeLayout được phân lớp của tôi để xử lý xoay vòng bản đồ của tôi?

Đề xuất và gợi ý được chào đón nhiều nhất.

UPDATE:

Trong một nỗ lực để tìm một giải pháp chấp nhận được cho vấn đề này, tôi đã cố gắng để thay đổi kích thước của canvas. Suy nghĩ là với kích thước canvas lớn hơn kích thước màn hình thực tế, tôi có thể di chuyển các góc trống ra khỏi màn hình hoàn toàn. Thật không may, có vẻ như không phải là một tùy chọn canvas.size() thực tế; tốt nhất tôi tìm thấy là canvas.scale().

Sử dụng canvas.scale(), tôi đã có thể tăng quy mô canvas theo hệ số 2 theo cả chiều ngang cũng như chiều dọc. Tuy nhiên, điều này có nghĩa là hình ảnh được phóng to một cách hiệu quả, khiến cho pixelation không được chấp nhận đối với các lát bản đồ.

Có ai biết kích thước canvas được khai báo không và liệu việc thay đổi kích thước canvas có thực sự giải quyết được sự cố của tôi không?

Trả lời

3

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!

+0

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

+0

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! –

Các vấn đề liên quan