【发布时间】:2011-06-29 08:04:42
【问题描述】:
能否获取其他指针的 MotionEvent.getRawX()/getRawY() 的值?
MotionEvent.getRawX() api reference
api说使用getRawX/getRawY获取原始的原始X/Y坐标,但它只针对1个指针(最后触摸的指针),是否可以获取其他指针的原始X/Y坐标?
【问题讨论】:
标签: android multi-touch
能否获取其他指针的 MotionEvent.getRawX()/getRawY() 的值?
MotionEvent.getRawX() api reference
api说使用getRawX/getRawY获取原始的原始X/Y坐标,但它只针对1个指针(最后触摸的指针),是否可以获取其他指针的原始X/Y坐标?
【问题讨论】:
标签: android multi-touch
确实,API 不允许这样做,但您可以计算它。试试看:
public boolean onTouch(final View v, final MotionEvent event) {
int rawX, rawY;
final int actionIndex = event.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT;
final int location[] = { 0, 0 };
v.getLocationOnScreen(location);
rawX = (int) event.getX(actionIndex) + location[0];
rawY = (int) event.getY(actionIndex) + location[1];
}
【讨论】:
对于大多数用例而言,值得尝试的解决方案是将其添加到 onTouchEvent 的第一行,它只是找到原始和已处理之间的差异,并将 MotionEvent 事件的位置移动该数量。这样所有的 getX(int) 值现在都是原始值。然后,您实际上可以使用 getX() getY() 东西作为原始值。
event.offsetLocation(event.getRawX()-event.getX(),event.getRawY()-event.getY());
虽然 Ivan 的观点是正确的,但只是将矩阵直接应用于视图本身很糟糕,您可能不应该这样做。设备之间很奇怪且不一致,导致触摸事件消失并被拒绝等。如果您正在像这样移动视图,最好简单地重载 onDraw() 并将该矩阵应用于画布,然后将逆矩阵应用于 MotionEvent,使一切都正确啮合。然后,您可以通过适当的细粒度控制对事件做出适当的反应。而且,如果你这样做,我的解决方案就不会受到 Ivan 的反对。
【讨论】:
如果视图旋转,仅将局部坐标移动视图的位置可能是不够的。在这种情况下,您需要这样的东西:
void getRowPoint(MotionEvent ev, int index, PointF point){
final int location[] = { 0, 0 };
getLocationOnScreen(location);
float x=ev.getX(index);
float y=ev.getY(index);
double angle=Math.toDegrees(Math.atan2(y, x));
angle+=getRotation();
final float length=PointF.length(x,y);
x=(float)(length*Math.cos(Math.toRadians(angle)))+location[0];
y=(float)(length*Math.sin(Math.toRadians(angle)))+location[1];
point.set(x,y);
}
【讨论】:
getLocationOnScreen 答案大部分时间都有效,但我有时会看到它返回不正确的值(当我在触摸事件发生时重新定位和重新设置视图时),所以我找到了另一种方法工作更可靠。
如果您查看getRawX 的实现,它会调用一个接受pointerIndex 的私有本机函数,但MotionEvent 类只使用索引0 调用它:
public final float getRawX() {
return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
}
不幸的是,nativeGetRawAxisValue 是私有的,但您可以通过使用反射来解决这个问题,让自己访问所需的一切。代码如下所示:
private Point getRawCoords(MotionEvent event, int pointerIndex) {
try {
Method getRawAxisValueMethod = MotionEvent.class.getDeclaredMethod(
"nativeGetRawAxisValue", long.class, int.class, int.class, int.class);
Field nativePtrField = MotionEvent.class.getDeclaredField("mNativePtr");
Field historyCurrentField = MotionEvent.class.getDeclaredField("HISTORY_CURRENT");
getRawAxisValueMethod.setAccessible(true);
nativePtrField.setAccessible(true);
historyCurrentField.setAccessible(true);
float x = (float) getRawAxisValueMethod.invoke(null, nativePtrField.get(event),
MotionEvent.AXIS_X, pointerIndex, historyCurrentField.get(null));
float y = (float) getRawAxisValueMethod.invoke(null, nativePtrField.get(event),
MotionEvent.AXIS_Y, pointerIndex, historyCurrentField.get(null));
return new Point((int)x, (int)y);
} catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException|
NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
当然,MotionEvent 内部没有记录,所以这种方法可能会在过去或未来版本的 SDK 上崩溃,但它似乎对我有用。
编辑:看起来mNativePtr 的类型和nativePtr 参数在 API 级别 20 中从 int 更改为 long,所以如果您的目标是 API 级别 19或更早,上面的代码会崩溃,因为getDeclaredMethod 找不到任何东西。为了在我的代码中解决这个问题,我只是按名称而不是完整的类型签名来获取方法,这恰好在这种情况下有效。没有办法直接查找具有给定名称的方法,因此我在静态初始化时遍历声明的方法并将匹配的方法保存到静态字段。代码如下:
private static final Method NATIVE_GET_RAW_AXIS_VALUE = getNativeGetRawAxisValue();
private static Method getNativeGetRawAxisValue() {
for (Method method : MotionEvent.class.getDeclaredMethods()) {
if (method.getName().equals("nativeGetRawAxisValue")) {
method.setAccessible(true);
return method;
}
}
throw new RuntimeException("nativeGetRawAxisValue method not found.");
}
然后我用NATIVE_GET_RAW_AXIS_VALUE 代替了上面代码中的getRawAxisValueMethod。
【讨论】:
没有 API 可以获取指针的特定 RawX 和 RawY。 但是您可以计算关于 View 相对于父级的位置及其旋转的类似值。 如果父视图占据了您要处理的整个触摸区域,使用 Matrix 将帮助您解决问题:
private float[] calcRawCoords(MotionEvent event, int pointerIndex) {
Matrix screenMatrix = new Matrix();
screenMatrix.postRotate(getRotation(), mPivotX, mPivotY);
screenMatrix.postTranslate(getLeft(), getTop());
float viewToScreenCoords[] = {event.getX(pointerIndex), event.getY(pointerIndex)};
screenMatrix.mapPoints(viewToScreenCoords);
return viewToScreenCoords;
}
【讨论】: