Android MotionEvent Pointer Index Confusion - java

I have a problem with my Android app that requires touch tracking events (tracking when/where finger goes down, move, up, etc). I have to use event.getX() and event.getY() functions to get the current touch's coordinates.
So from what I've learned in the past few months:
MotionEvent.ACTION_DOWN tracks the first touch down
MotionEvent.ACTION_POINTER_DOWN tracks subsequent touches down
MotionEvent.ACTION_UP tracks the last touch up
MotionEvent.ACTION_POINTER_UP tracks touch that goes up
MotionEvent.ACTION_MOVE tracks any touch movement
In my app, I'm encountering a significant problem when my first touch goes up. Lets say I have five fingers touching my device (lets call these Touch 0, 1, 2, 3, 4). Now, when I lift up Touch 0, MotionEvent.ACTION_POINTER_UP is the action I get. Totally understandable; I get this. However, now these two things will happen:
Move anything from Touches 1-4: Get an IllegalArgumentException telling me the pointerIndex is out of range
Lift up anything from Touches 1-4: Spits back information for a different touch (event.getX() and event.getY() will give me a different finger information)
I'm kind of at my wit's end on how to properly track this information. Any clues on how to properly track the information or offset the touch pointers?
I provided the general layout of what my code is but I feel like I'm not doing anything out of the ordinary (and I'm sure it is close to the example code on the Android examples?):
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getActionMasked();
int ptr_idx = event.getPointerId(event.getActionIndex());
try {
switch (action) {
case MotionEvent.ACTION_MOVE:
handleActionPointerMove(event);
break;
// Order of touch downs doesn't matter to us
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
handleActionPointerDown(event, ptr_idx);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
handleActionPointerUp(event, ptr_idx);
break;
}
}
catch (IllegalArgumentException e) {
e.printStackTrace();
return false;
}
return true;
}
}
public void handleActionPointerMove(MotionEvent event) {
for (int i = 0; i < event.getPointerCount(); i++) {
handleInformation(event.getX(i), event.getY(i));
}
}
public void handleActionPointerDown(MotionEvent event, int ptr_idx) {
handleInformation(event.getX(ptr_idx), event.getY(ptr_idx));
}
public void handleActionPointerUp(MotionEvent event, int ptr_idx) {
handleInformation(event.getX(ptr_idx), event.getY(ptr_idx));
}
public void handleInformation(int x, int y) {
// Figures out what x+y means to us (are we in a certain box within the app? outside in clear zones? etc
}

Well after thinking about it for a night I've gotten it to work doing this before calling MotionEvent.getX() and MotionEvent.getY():
public int getAdjustment(int ptr_idx) {
int adjust = 0;
for (int i = 0; i < event.getPointerCount(); i++) {
// Get Actual Pointer Index of touch
int adjustedPointerIndex = event.getPointerId(i);
// If we've found the current touch's pointer index AND
// the pointer index doesn't equal the sequential event's
// pointers
if ((adjustPointerIndex == ptr_idx) && (i != adjustPointerIndex)) {
adjust = (adjustPointerIndex - i);
break;
}
}
return adjust;
}
// Example Function using getAdjustment(int ptr_idx)
public void handleActionPointerUp(MotionEvent event, int ptr_idx) {
int adjustment = ptr_idx - getAdjustment(ptr_idx);
handleInformation(event.getX(adjustment), event.getY(adjustment));
}
Explanation of the for loop statement:
Basically, lets say we have 4 touches (Touch 0, 1, 2, 3). Now we have lifted up Touch 0 and now we have Touch 1, 2, 3. Android will see these touches as 0, 1, 2 and the actual pointer indices as 1, 2, 3. To get the correct adjustment, I iterate through the MotionEvent's pointers (this is 0, 1, 2 right now in the for loop).
Now, if for some reason Touch 0 or any touch in between is taken out, we must adjust the pointer index as getX() and getY() doesn't understand the touch's actual pointer index. It only takes in indices 0, 1, 2 even though we have pointer indices 1, 2, 3. Thus, if we've reached the correct current touch pointer index BUT the MotionEvent's touch index does not equal correct touch pointer index, we adjust it by doing adjust = adjustPointerIndex-i.
After doing that, just subtract it from the current ptr_idx we're analyzing and we get a value that getX() and getY() can understand without IllegalArgumentException for a pointerIndex out of range.
Will explain more thoroughly if that doesn't make sense and if someone has a way more elegant solution please let me know because I'm sure this is not a great way to handle this. I'd rather mark someone else's answer as the approved answer.

Related

Switch statement , only using one case at a time

Im trying to translate a square 'p' 5 units left or right depending on what key has been pressed . The problem is that when 'right' or 'left' is pressed it will translate 5 units in that direction but when I press again I can't move and I have to press 'left' to move right again so I never get anywhere.
Does anyone know why this is?
Task PlyThread=new Task() {
#Override
protected Object call() throws Exception {
myScene.setOnKeyPressed(event -> {
switch (event.getCode()){
//case UP: p.setTranslateY(((p.getY())-5)) ; break;
case LEFT: p.setTranslateX(((p.getX())-5)) ; break;
case RIGHT: p.setTranslateX(((p.getX())+5)) ;break;
}
});
return null;
}
};new Thread(PlyThread).start();
Ok, I assume that "Rectangle" is javafx.scene.shape.Rectangle. In this case setTranslateX() only modifies transformation matrix and does not change the rectangle coordinate X. Value of property X remains unchanged and next call to setTranslateX(getX()+5) does exactly the same job as the one before.
You need to work either with translations or with coordinates, i.e. either use setTranslateX()/getTranslateX() or setX()/getX().
Both choices may have other consequences, beyond moving rectangle on the screen, but I have no experience with JavaFX, so unfortunately I cannot elaborate more.

LibGDX player movement

I'm developing a 2D space shooter game with the player only moving left and right. I've rendered a button on the left and the right side of the screen and constantly check if it's touched. The problem with this is that you have to lift your finger of the button in order to press the button on the other side of the screen. I want the ship to be moving towards the last touched part of the screen(Even when you actually didn't lift the finger of your first-touched button).
public void keyListener(float delta){
//right movement
if(Gdx.input.isKeyPressed(Keys.RIGHT) || (Gdx.input.isTouched() && game.cam.getInputInGameWorld().x >= arrowMoveX - 40) && !isPlayerHit)
x+=SPEED*Gdx.graphics.getDeltaTime();
//left movement
if(Gdx.input.isKeyPressed(Keys.LEFT) || (Gdx.input.isTouched() && game.cam.getInputInGameWorld().x < arrowMoveWhite.getWidth() + 40) && !isPlayerHit)
x-=SPEED*Gdx.graphics.getDeltaTime();
I've tried puting another if statements in these to check for a second movement, but this way it only works on one direction.
Can you please help me?
What you need to know about for this is that Android indexes each separate touch in order of when the screen was touched, the index is known as a "pointer". For example, when you touch the screen with only one finger, the touch pointer is 0, and the second touch pointer is 1. The highest pointer that libGDX registers is 20.
For your particular situation, you want to read only the input that is on the highest pointer that is currently reading a touch, and have an int reading the highest touch. You can loop through the pointers, and set the int to whatever touch event is actually the highest pointer which is referring to the most recent press like this:
int highestpointer = -1; // Setting to -1 because if the pointer is -1 at the end of the loop, then it would be clear that there was no touch
for(int pointer = 0; pointer < 20; pointer++) {
if(Gdx.input.isTouched(pointer)) { // First check if there is a touch in the first place
int x = Gdx.input.getX(pointer); // Get x position of touch in screen coordinates (far left of screen will be 0)
if(x < arrowMoveWhite.getWidth() + 40 || x >= arrowMoveX - 40) {
highestpinter = pointer;
} // Note that if the touch is in neither button, the highestpointer will remain what ever it was previously
}
} // At the end of the loop, the highest pointer int would be the most recent touch, or -1
// And to handle actual movement you need to pass the highest pointer into Gdx.input.getX()
if(!isPlayerHit) { // Minor improvement: only check this once
if(Gdx.input.isKeyPressed(Keys.RIGHT) || (highestpointer > -1 && Gdx.input.getX(highestpointer) >= arrowMoveX - 40)) {
x+=SPEED*Gdx.graphics.getDeltaTime();
} else if(Gdx.input.isKeyPressed(Keys.LEFT) || (highestpointer > -1 && Gdx.input.getX(highestpointer) < arrowMoveWhite.getWidth() + 40)) {
x-=SPEED*Gdx.graphics.getDeltaTime();
}
}
Note that you will probably want a separate camera drawing your buttons (or any hud elements) because you would not need to worry about translating screen coordinates to world coordinates since x goes in the same direction.
Let me know how that works and if you need any changes!

Getting current fingers position on Android

I'm developing an application that needs to get all fingers on the screen of the Android. That's not complex, and I could make it in a few lines of code.
However, the way android sends callbacks is not what I need. I have done many tests, and here is what I could find out:
Once he sent the fingers positions, if no "big" movement has been made, he will not send a onTouch event.
Also, when a finger is REMOVED from the screen (for example: there are 3 fingers, and one is removed), it seems that it ONLY sends the next event, if at least one of the remaining fingers move on the screen.
I'm doing some tracking on fingers, and matching with objects, and to do this properly, I need to know all the fingers positions all the time. If there is not a way to "request" finger's touch event even when it didn't moved, how can I access the current finger positions without an callback event? Is there any other solution?
Here is my code:
ArrayList<Vector3> fingerTips = new ArrayList<Vector3>();
#Override
public boolean onTouchEvent(MotionEvent event) {
final int points = event.getPointerCount();
String out = "==========\n";
fingerTips.clear();
for(int i = 0; i < points; i++){
out += "\tPoint "+i+"\t("+event.getX(i)+", "+event.getY(i) + ")\n";
fingerTips.add( new Vector3(event.getX(i), event.getY(i)) );
}
Log.i(TAG, out);
// Send touches to SurfaceView
chwaziViewGL.onUpdateFingerTips(fingerTips);
return true;
}
My problem was that I didn't received one event with all the finger positions AFTER removing a finger...
After lot's of tests and logging, I discovered that it's true (in part).
Whenever you REMOVE a finger on the SCREEN, the NEXT event sent is an ACTION_POINTER_UP or ACTION_UP, meaning that the finger x is no longer touching the screen.
Since there was NO motion after removing the finger, the last MotionEvent sent was the UP, containg the removed finger also.
So, to fix that, I check if the action is UP, and on the loop that get's all the fingers, I created an if checking if that was the finger removed from the screen. If so, I just didn't add it to the array.
Here is my final code:
ArrayList<Vector3> fingerTips = new ArrayList<Vector3>();
#Override
public boolean onTouchEvent(MotionEvent event) {
final int points = event.getPointerCount();
// Check if it's an event that a finger
// was removed, if so, set removedPoint
int removedPoint = -1;
final int action = event.getAction() & MotionEvent.ACTION_MASK;
if(action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_UP)
removedPoint = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
String out = "==========\n";
fingerTips.clear();
for(int i = 0; i < points; i++){
// Find out pointer ID
int pointerID = event.getPointerId(i);
if(pointerID == MotionEvent.INVALID_POINTER_ID){
out += "\tPoint "+pointerID+" INVALID\n";
continue;
}
// Check if it's the removed finger
if(removedPoint == i){
out += "\tPoint "+pointerID+" REMOVED\n";
continue;
}
out += "\tPoint "+pointerID+"\t("+event.getX(i)+", "+event.getY(i) + ")\n";
fingerTips.add( new Vector3(event.getX(i), event.getY(i), pointerID) );
}
Log.i(TAG, out);
// Send touches to SurfaceView
chwaziViewGL.onUpdateFingerTips(fingerTips);
return true;
}
Sorry, but your code cost me a lot of hairs;-(( After days I found the problem finally before I got insane. This code
final int action = event.getAction() & MotionEvent.ACTION_MASK;
if(action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_UP)
removedPoint = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
could have never been worked!!!
Why? Because if you mask the value that is coming from getAction() it is simply a plain integer value of the action. And when you try to use this plain value and masking it again with something, it has no meaning at all!
The correction is to use again event.getAction() in the seconds masking:
removedPoint = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
It was really a nightmare to find this. But anyway, I think you just wanted the best and help others:-))) Its ok.

Android - How to distinguish first pointer and second pointer move in onTouchEvent

I want handle a gesture in my activity. To do this i have override the public boolean onTouchEvent(MotionEvent MEvent) method on my Activity. The content looks like this:
motionaction = MEvent.getAction();
if(motionaction == MotionEvent.ACTION_DOWN)
{
...
return true;
}
if(motionaction == MotionEvent.ACTION_UP)
{
...
return true;
}
if(motionaction == MotionEvent.ACTION_MOVE)
{
...
return true;
}
motionaction = MEvent.getActionMasked();
if(motionaction == MotionEvent.ACTION_POINTER_DOWN)
{
...
return true;
}
if(motionaction == MotionEvent.ACTION_POINTER_UP)
{
...
return true;
}
return true;
The gesture it's the follow:
-finger1 on the screen hold its position (virtually because there is always a little movement)
-finger2 move on the screen. This is the movement that i want to grab.
I can grab the 5 action but the problem it's that when two fingers are on the screen the ACTION_MOVE grabs the movement of both first and second finger. The method MEvent.getActionIndex() don't works for the ACTION_MOVE that is return always 0; The only thing that i can do it's to save the position of the finger1 and to discard the movement near to that point. The result it's not perfect indeed sometime the movement of the finger2 it's "tainted" by the little finger1 movement because though the finger holds it's position on the screen the listener feels each minimal movement.
How can i improve this ?
Referring to the answer of Quintin Balsdon:
I have a similar thing in my code. I save the position of the finger1 in the ACTION_DOWN case, then when the finger2 move i see if the Y coordinate of the move is over the saved finger1 Y coordinate. If so the movement it's referred to the finger2 otherwise is referred to finger1 and i discard that in two finger mode.
If i try to draw a circle on my view in one finger mode i have got something like this:
http://img208.imageshack.us/img208/8113/onefingercircle.jpg
If i try to draw a circle on my view in two finger mode i have got something like this:
http://img256.imageshack.us/img256/6778/twofingercircle.jpg
So in one finger mode it work perfectly but not in two finger mode.
I don't know if it's related to the phone multitouch handler or the touch screen. It can be only hardware related also or my misunderstanding of the API.
This is managed by you as the developer. You are going to have to create some boolean variable to set (to true) when the "DOWN" action takes place and then unset (false) when the "UP" or "MOVE" motionevent occurs. In the code below, I maintain the coordinates when the "DOWN" even happens and make adjustments while the user moves around.
switch (e.Action) //e is a MotionEvent type
{
case MotionEvent.ACTION_DOWN:
{
_prevx = e.getX();
_prevy = e.getY();
}
break;
case MotionEvent.ACTION_MOVE:
{
_xoffset += e.GetX() - _prevx;
_yoffset += e.GetY() - _prevy;
Invalidate();
_prevx = e.GetX();
_prevy = e.GetY();
}
break;
}
If you want to do multi-finger dragging you need to implement ScaleGestureDetector.OnScaleGestureListener (http://developer.android.com/reference/android/view/ScaleGestureDetector.OnScaleGestureListener.html)

Reason why my loop to find an object in an Array only works for the first time?

I am creating a program that displays several bases and the amount of troops each base has. There are two types of bases, friendly and enemy bases. Each Base extends GCompound and consists of a GRect and a GLabel(to display the number of troops). Two arrays are used to keep track of the bases, one for friendly, one for enemy.
I want the user to be able to press the mouse down on one friendly base and release on a different friendly base, causing the troop amount to be transferred from the first base to the second one.
My problem currently is I am only able to detect the base the user presses the mouse down on, and not the base that the mouse is released on. I am using the method getElementAt from the ACM library to return the GObject that a mouse action takes place on.
Code for the mouse press:
public void mousePressed(MouseEvent e){
int mouseX = e.getX();
int mouseY = e.getY();
for(int i = 0; i < PlayerBaseArray.length; i++){
if(getClickedObject(mouseX, mouseY) == PlayerBaseArray[i]){ //Checks to see if the clicked base is in the Array of friendly bases.
pressedIndex = i;
pressedBaseTroopCount = PlayerBaseArray[pressedIndex].getTroopCount();
}
}
}
Code for the mouse release:
public void mouseReleased(MouseEvent m){
int mouseX = m.getX();
int mouseY = m.getY();
for(int i = 0; i < PlayerBaseArray.length; i++){
if(getClickedObject(mouseX, mouseY) == PlayerBaseArray[i]){ // This is always false for some reason.
PlayerBaseArray[i].changeTroopCount(pressedBaseTroopCount);
PlayerBaseArray[pressedIndex].changeTroopCount(-pressedBaseTroopCount);
}
}
}
Method to see what object is clicked:
private GObject getClickedObject(int x, int y){
GObject clicked = getElementAt(x, y);
if(clicked == null) return null;
else return clicked;
}
For some reason the if statement in mouseReleased() is never true, even though it works properly in mousePressed(). Any idea on why the if statement in mouseReleased() does not work?
I've tried researching the problem to no avail, and instead of wasting another night on it I thought I would ask here. Thanks!
You shouldn't use the == operator to compare Objects. You should use the .equals() method:
if (getClickedObject(mouseX, mouseY).equals(PlayerBaseArray[i]))
Put simply, you can only use == only when comparing java primitives (eg int, long etc).
For all "normal" objects, always use objecta.equals(objectb).
This article discusses this issue in more detail.
Attention anal retentives:
Yes, you are right, under some circumstances you can safely use == between objects such as String contants, Integers between -128 and 127, etc, etc. But this guy needed a clear answer and not to be confused. Please resist the temptation to comment about this.

Categories