how do you use MotionEvent.ACTION_POINTER_INDEX_SHIFT?
Asked Answered
V

3

6

I've been working in a game and I'm trying to make the controllers, nothing too complicated and to do this I need to track 2 inputs(fingers): 1 the fire button and move keys.(up, down, left, right)

This is the problem: finger 1 is down, finger 2 is down, finger 1 goes up thinking it's 2 and then finger 2 goes up thinking it's 1.

D/Controlls(18849): Action Down 1
D/Controlls(18849): Coordinates 267.7908 415.24274
D/Controlls(18849): Action Pointer Down 2
D/Controlls(18849): Coordinates 281.11423 417.23993
D/Controlls(18849): Action Pointer UP 1
D/Controlls(18849): Coordinates 272.7869 419.23718
D/Controlls(18849): Action UP 2
D/Controlls(18849): Coordinates 1148.103 439.20947

This is the code for the OnTouchEvent which handles the 2 inputs:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int index = event.getActionIndex();
    int pointerId = event.getPointerId(index);
    int action = event.getActionMasked();

    int oldX, oldY;

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
        {
            hero.moveControlls((int)event.getX(), (int)event.getY());
            Log.d("Controlls", "Action Down "+ pointerId);
            Log.d("Controlls", "Coordinates "+ event.getX() + " "+ event.getY());
            break;
        }

        case MotionEvent.ACTION_UP:
        {
            hero.setScreenTouching(false);
            Log.d("Controlls", "Action UP "+ pointerId);
            Log.d("Controlls", "Coordinates "+ event.getX() + " "+ event.getY());
            break;
        }

        case MotionEvent.ACTION_POINTER_DOWN:
        {
            Log.d("Controlls", "Action Pointer Down "+ pointerId);
            Log.d("Controlls", "Coordinates "+ event.getX() + " "+ event.getY());                   
            break;
        }

        case MotionEvent.ACTION_POINTER_UP:
        {
            Log.d("Controlls", "Action Pointer UP "+ pointerId);
            Log.d("Controlls", "Coordinates "+ event.getX() + " "+  event.getY());
            break;
        }
    }
    return true;
}

Now, I looked up in the examples, but could not understand them. I looked up MotionEvent in the API and it says to use $ACTION_POINTER_INDEX_SHIFT$ which I have no clue how to use, because they don't have an example or something to make it understood easier. Any help on how to do this?

Ventral answered 18/1, 2013 at 3:8 Comment(0)
C
14

ACTION_POINTER_DOWN and ACTION_POINTER_UP are fired whenever a secondary pointer goes down or up. If there is already a pointer on the screen and a new one goes down, you will receive ACTION_POINTER_DOWN instead of ACTION_DOWN. If a pointer goes up but there is still at least one touching the screen, you will receive ACTION_POINTER_UP instead of ACTION_UP.

The ACTION_POINTER_DOWN and ACTION_POINTER_UP events encode extra information in the action value. ANDing it with MotionEvent.ACTION_MASK gives us the action constant while ANDing it with ACTION_POINTER_INDEX_MASK gives us the index of the pointer that went up or down

The best way to extract the index of the pointer that left the touch sensor.

int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;

I would change your code accordingly like below:

case MotionEvent.ACTION_POINTER_UP:
{
    int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    int pointId = event.getPointerId(index);
    Log.d("Controlls", "Action Pointer UP "+ pointerId);
    Log.d("Controlls", "Coordinates "+ event.getX(index) + " "+  event.getY(index));
    break;
}
Ceraceous answered 22/2, 2013 at 17:50 Comment(1)
it's a bit cleaner to use MotionEventCompat.getActionIndexAnd
T
2

I think the confusion may be that the methods getX() and getY() always return the coordinates of the primary pointer (the first/only one down). So the first three events in your log finger 1 is the primary, and then in the last even only finger 2 is left, so it becomes the primary.

If you want to properly track the events for both fingers, you will need to use getX(int) and getY(int); the versions that take a pointer index as a parameter so you can discretely get the coordinates for each finger in each event. Note that the indices of each finger may change if they go down and up in a different order, but a given finger will always have the same pointer Id.

A good approach would be to check the number of pointers in each event (e.g. getPointerCount()). With one finger, index 0 is valid; with two fingers, index 0 and 1 are valid. Go get the coordinates for each valid pointer, and then use getPointerId() to figure out which finger each coordinate pair should be matched to.

EDIT

The simplest modification to the source code you've posted to return the coordinates for the right pointer is to add the index to the parameters for secondary pointers, like so:

int index = event.getActionIndex();
int pointerId = event.getPointerId(index);
int action = event.getActionMasked();

int oldX, oldY;

switch (event.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
    {
        hero.moveControlls((int)event.getX(), (int)event.getY());
        Log.d("Controlls", "Action Down " + pointerId);
        Log.d("Controlls", "Coordinates "+ event.getX() + " "+ event.getY());
        break;
    }

    case MotionEvent.ACTION_UP:
    {
        hero.setScreenTouching(false);
        Log.d("Controlls", "Action UP "+ pointerId);
        Log.d("Controlls", "Coordinates "+ event.getX() + " "+ event.getY());
        break;
    }

    case MotionEvent.ACTION_POINTER_DOWN:
    {
        Log.d("Controlls", "Action Pointer Down "+ pointerId);
        Log.d("Controlls", "Coordinates "+ event.getX(index) + " "+ event.getY(index));
        break;
    }

    case MotionEvent.ACTION_POINTER_UP:
    {
        Log.d("Controlls", "Action Pointer UP "+ pointerId);
        Log.d("Controlls", "Coordinates "+ event.getX(index) + " "+  event.getY(index));
        break;
    }
}

This should log data more inline with what you were expecting to see.

Topographer answered 18/1, 2013 at 3:32 Comment(4)
i kinda get what you are saying but still cant implemented do you mind showing me an example ?Ventral
I have modified your switch statement.Topographer
I'm having some similar problems. Would you mind explaining what is happening with "ANDing" the action with MotionEvent.ACTION_POINTER_INDEX_MASK (this occurs in every example on this topic) and what the purpose of the shift is? I'm also not clear on what these Masks are about and the documentation is cryptic. I'd greatly appreciate that and upvote for more clarity. Thanks!Burgener
@Burgener it's a bitwise mask. It's to allow multiple values to be stored inside the same value, bit shifted by some offset. More info: en.wikipedia.org/wiki/Mask_(computing)#Masking_bits_to_0Rosettarosette
O
0

The above code may fail if you release pointers in different order than press (what may happens when using multitouch). I found that following code works for 3 pointers and the order of pressing then releasing does not matter:

    case MotionEvent.ACTION_UP: // for single touch
    case MotionEvent.ACTION_POINTER_UP: // when order of touch and release is the same
    case MotionEvent.ACTION_POINTER_UP + ((1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT)): // for any order of two pointers
    case MotionEvent.ACTION_POINTER_UP + ((2 << MotionEvent.ACTION_POINTER_INDEX_SHIFT)): // for any order of three pointers
    case MotionEvent.ACTION_OUTSIDE: // when moving touch out of the view
    case MotionEvent.ACTION_CANCEL: // when canceling touch (eg by home button)
    // some code to support releasing pointers
    break;

Remember that next to case statement that must be a constant value so you can't calculate value based on event.getMotion().

Outbuilding answered 14/1, 2014 at 21:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.