Home » Android » android – Get MotionEvent.getRawX/getRawY of other pointers

android – Get MotionEvent.getRawX/getRawY of other pointers

Posted by: admin May 14, 2020 Leave a comment

Questions:

Can I get the value of MotionEvent.getRawX()/getRawY() of other pointers ?

MotionEvent.getRawX() api reference

The api says that uses getRawX/getRawY to get original raw X/Y coordinate, but it only for 1 pointer(the last touched pointer), is it possible to get other pointer’s raw X/Y coordinate ?

How to&Answers:

Indeed, the API doesn’t allow to do this, but you can compute it. Try that :

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];

}

Answer:

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.

Answer:

It might be not enough to just shift local coordinates by a view’s location if the view is rotated. In this case you need something like this:

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);
}

Answer:

A solution worth trying for most use cases is to add this to the first line of the onTouchEvent it simply finds the difference between the raw and processed, and shifts the location of the MotionEvent event, by that amount. So that all the getX(int) values are now the raw values. Then you can actually use the getX() getY() stuff as the Raw values.

event.offsetLocation(event.getRawX()-event.getX(),event.getRawY()-event.getY());

While Ivan’s point is valid, it’s simply the case that applying a matrix directly to the view itself sucks so bad you likely shouldn’t do it. It’s weird and inconsistent between devices, cause the touch events to fall out of view and get declined, etc. If you are moving a view around like that you are better off simply overloading the onDraw() and applying that matrix to the canvas, then applying the inverse matrix to the MotionEvent so everything meshes up right. Then you can properly react to the events with proper and fine grain control. And, if you do that, my solution here wouldn’t be subject to Ivan’s objection.

Answer:

There is no API to get pointer’s specific RawX and RawY.
But you can calculate similar values with regards to View’s position to the parent and its rotation.
In case if parent view occupies the entire touch area you are trying to handle, using Matrix will help you to solve your problem:

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;
}