- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我正在开发一个应用程序,该应用程序使用 OpenStreetMap (OSM) API 在由静态图 block 组成的离线 map 上显示各种兴趣点。
我目前正在实现的功能之一是根据手机(通过 GPS)确定的方位旋转 map 。我能够毫不费力地实现实际的旋转,但是由于我的代码旋转了整个 Canvas ——这可能是一种相当幼稚的方法——我现在在屏幕上有空白的角落,没有加载新的图 block 来弥补这一事实旋转的图 block 不再填充这些像素。
经过一些谷歌搜索后,我找到了一些关于如何解决这个问题的建议,但到目前为止还没有成功。
在 Mr. Romain Guy's posts 之一中,他提到了以下内容:
I have done this in the past and it requires to create a custom ViewGroup that rotates the Canvas in the dispatchDraw() method. You also need to increase the size of the MapView (so that it draws enough pixels when rotated.) You will also need to rotate the touch events in dispatchTouchEvent(). Or if you use Android 3.0 you can simply call theMapView.rotate()
根据他关于 map 旋转的建议,我已经按照建议实现了 dispatchDraw() 和 dispatchTouchEvent(),但是我在他提到我需要增加 MapView 的大小的部分遇到了问题?
这是我在 XML 文件中所做的事情吗,如 this thread 中所建议的那样?
或者,我能否以某种方式重写处理 map 旋转的子类 RelativeLayout 类中的 onMeasure() 函数?
非常欢迎提出建议和提示。
更新:
为了找到解决此问题的可接受的解决方案,我尝试更改 Canvas 的大小。当时的想法是,如果 Canvas 尺寸大于实际屏幕尺寸,我或许可以将空白角完全移出屏幕。不幸的是,似乎没有实际的 canvas.size() 选项;我发现最好的是 canvas.scale()。
使用 canvas.scale(),我能够在水平和垂直维度上将 Canvas 的比例增加 2 倍。然而,这意味着图像实际上被放大了,导致 map 图 block 出现 Not Acceptable 像素化。
有谁知道在哪里声明 Canvas 的大小,如果改变 Canvas 的大小实际上可以解决我的问题?
最佳答案
我最终听从了 Romain Guy 的建议(与我在问题中发布的建议相同)。也就是说,我通过扩展 RelativeLayout
创建了我自己的自定义 ViewGroup
,然后我增加了我的 mapView
的大小以覆盖整个屏幕。扩展 RelativeLayout ViewGroup
是必要的,这样我也可以覆盖 dispatchDraw(...)
、onMeasure(...)
作为 dispatchTouchEvent(...)
函数为我的应用程序启用所需的 map 旋转功能。
dispatchDraw(...)
函数本质上是拦截对 onDraw(...)
函数的调用,对该函数的输入执行一些特定的操作,然后然后释放它进行处理。在我们的例子中,我们希望在 mapView
Canvas 进入实际的 onDraw(...)
函数之前旋转它。这就是为什么我们需要重写这个函数。
具体来说,dispatchDraw(...)
函数将 Canvas 对象作为输入,该对象(在本例中)表示 OSM mapView
对象(如定义在下面的 XML 文件)。如果要对 Canvas 应用旋转,我们需要定位 map 的中心,平移(即移动) map ,使 map 的中心位于坐标系的原点,旋转 map 坐标系的原点,然后,最后,我们要将这个修改后的 Canvas 分派(dispatch)到渲染管道的下一个阶段。
我的代码如下;请注意,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
Canvas 的任何旋转最终都会旋转,而不仅仅是 map 的图形表示,还有与该 Activity 相关的所有其他内容(这是我的特定实现的副作用);也就是说,触摸事件坐标在旋转后仍然相对于 mapView
Canvas ,而不是相对于实际手机。例如,如果我们想象 Canvas 旋转了 180 度,那么如果用户试图向左平移 map ,它就会移动到向右的 map ,因为一切都是颠倒的!
在代码中,您可以按如下方式更正此问题:
/**
* @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()
最后,要使 map Activity 的相应 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>
我想说的是,这个解决方案绝不是最好的解决方案,但它对我的概念验证应用程序来说效果很好!
关于Android:获取旋转的 OSM 贴图以填充整个屏幕,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7237276/
我最近为我的一个 iPhone 应用程序上传了更新,现在该应用程序刚刚准备好销售,但我只更新 iPhone 6 的屏幕截图,而不更新其他屏幕截图。现在我明白了,它们是新游戏的旧屏幕,这可能会让用户感到
我需要清除 Scala 中的控制台屏幕 我尝试过标准 ANSI Clear 屏幕,rosettacode.org 建议将其称为“终端控制/清除屏幕”here object Cls extends Ap
我需要清除 Scala 中的控制台屏幕 我尝试过标准 ANSI Clear 屏幕,rosettacode.org 建议将其称为“终端控制/清除屏幕”here object Cls extends Ap
我将从头开始,我正在开发一个跨越多个监视器的应用程序,每个监视器将包含一个 WPF 窗口,并且这些窗口使用一个 View 模型类进行控制。现在假设我在 200,300 (x,y) 处的所有窗口上都有一
谁能告诉我如何阻止矩形在我的游戏中离开面板(屏幕)?矩形随着击键并排移动。 最佳答案 这是你应该做的: 1. 跟踪矩形的 (x,y) 坐标。 2. 确保矩形的x + width 不大于JPanel 的
我正在尝试检测电源按钮是否在 4 秒内被按下 3 次。以下代码无效。 public class PowerButtonReceiver extends BroadcastReceiver{ s
我为我的新网上商店制作了一个横幅,但有一个问题。例如,当网站在我的笔记本电脑上显示为全尺寸时,横幅非常适合,但是当我在移动设备、笔记本电脑和较小尺寸的网站上看到该网站时,横幅就不合适了。我真的希望你们
我希望这个问题能够得到解决: 给定,屏幕 session 正在运行,并在终端中打开(附加)。 问题。 如果终端 session 终止,但未与屏幕 session 分离,则屏幕 session 中运行的
如何在点击通知时转到特定屏幕?我在 javascript 中使用了云功能,当我点击通知时,它打开了我的应用程序而不是特定的屏幕 _fcm.configure( onMessage: (Map m
Qt 确实支持像素比率 (devicePixelRatio),这在我的各种桌面上是不同的: ) 桌面 w1920 h1080 - 比例:1 ) 桌面 w3840 h2160 与 qputenv("QT
我一直在做一些研究,发现了这种情况。如果要写入STDOUT(屏幕),将无法执行多线程脚本,该脚本通过简单的单线程脚本可以更快地打印数据。但是,如果您写入这样的文件: myPrinter.perl >
我是 ABAP 的新手,我想制作一个具有多个屏幕和一个初始主屏幕的程序,可以在其中看到所有程序屏幕的列表。我知道我可以对它们进行硬编码,但应该有更好的方法。 如果有任何类型的字段/区域,我需要使此列表
我目前正在探索创建越狱调整。我想解锁手机屏幕。这是怎么做到的?在 iOS 7 上可以使用哪些私有(private) API 来实现这一点? 最佳答案 如果我们谈论越狱,那么您可以编写一个 Spring
我正在寻找一种方法来关闭 iPhone 屏幕而不让 iPhone 进入休眠状态。我真的不介意关闭屏幕是否违反苹果规则。将窗口 alpha 设置为 0 可以解决问题吗?我可以更改某种 boolean 值
我在 iTerm2 中使用 tmux。 当我在 bash 中时,使用 Ctrl-L 清除屏幕有效,但在我拖尾服务器日志时不起作用。我该如何解决? 最佳答案 您可以使用 send-keys -R 清除当
如何以编程方式锁定和解锁 iPhone 的主屏幕(即设备本身)? 最佳答案 这是不可能的。但是,您可以在应用程序运行时“阻止”手机锁定。 [UIApplication sharedApplicatio
我的任务是创建社交网络 我有一个主页,显示用户通过 Canvas 创建的所有绘图。用户可以在自己的绘图上绘图,并且可以添加也有权在其绘图上绘图的贡献者。问题是他们可以以某种方式同时绘制。 我想做的是,
退出后,如何从我在应用程序中打开的最后一个屏幕继续。 比如说我有屏幕A,B和C,并且在关闭应用程序之前我在屏幕B上。我的问题是当我再次打开应用程序时如何进入屏幕B。 最佳答案 当SharedPrefe
我试图停止一个进程,然后休眠10秒钟,杀死下一个进程再休眠10秒钟,然后启动另一个进程。问题是一切都立即运行。所以我想开始的过程没有运行,因为其他人都没有停止。 Start-Process Power
我正在尝试以 HW 身份进行测验。我输入的回复应该会出现在屏幕上,但事实并非如此。我使用函数response()多次检查了“15”行和“17”行中出了什么问题。我没有发现任何错误,需要调试和编写正确语
我是一名优秀的程序员,十分优秀!