The getLocationOnScreen
answer works most of the time, but I was seeing it return incorrect values sometimes (when I was repositioning and re-parenting the view while the touch event was taking place), so I found an alternate approach that works more reliably.
If you look at the implementation of getRawX
, it calls a private native function that accepts a pointerIndex
, but the MotionEvent
class only ever calls it with index 0:
public final float getRawX() {
return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
}
Unfortunately, nativeGetRawAxisValue
is private, but you can hack around that by using reflection to give yourself access to everything you need. Here's what the code looks like:
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);
}
}
Of course, the MotionEvent
internals aren't documented, so this approach might crash on past or future versions of the SDK, but it seems to be working for me.
Edit: It looks like the type of mNativePtr
and the nativePtr
param changed from int
to long
in API level 20, so if you're targeting API level 19 or earlier, the above code will crash because getDeclaredMethod
won't find anything. To fix this in my code, I just fetched the method by name instead of full type signature, which happens to work in this case. There isn't a way to directly look up methods with a given name, so I looped through the declared methods at static init time and saved the matching one to a static field. Here's the code:
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.");
}
Then I used NATIVE_GET_RAW_AXIS_VALUE
in place of the getRawAxisValueMethod
in the above code.