Get MotionEvent.getRawX/getRawY of other pointers
Asked Answered
H

5

11

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 ?

Haematoblast answered 29/6, 2011 at 8:4 Comment(0)
C
17

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

}
Cobb answered 28/3, 2012 at 18:25 Comment(1)
@GilSH the following implementation will work if the view is rotated: https://mcmap.net/q/428568/-get-motionevent-getrawx-getrawy-of-other-pointersSealey
G
6

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.

Groom answered 6/7, 2016 at 11:28 Comment(2)
Much simpler solution than the others, and spot on about overloading onDraw(). Trying to map touch points to a transformed View is a recipe for headaches.Karilynn
You sir, are an actual God, Tatarize! This solution has saved us from days of pain that we hadn't been able to solve. Wish I could upvote more!Hair
S
5

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);
}
Sw answered 2/7, 2013 at 22:47 Comment(0)
P
5

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.

Pitchdark answered 14/3, 2015 at 3:34 Comment(0)
S
2

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;
}
Sealey answered 15/8, 2017 at 22:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.