Getting current fingers position on Android - java

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.

Related

Getting current android gamepad axis position

I receive axes position from my bluetooth gamepad controller in dispatchGenericMotionEvent(android.view. MotionEvent) method.
My method:
#Override
public boolean dispatchGenericMotionEvent(final MotionEvent event) {
if( mPadListener==null ||
(event.getSource()&InputDeviceCompat.SOURCE_JOYSTICK)!=InputDeviceCompat.SOURCE_JOYSTICK ){
return super.dispatchGenericMotionEvent(event);
}
int historySize = event.getHistorySize();
for (int i = 0; i < historySize; i++) {
// Process the event at historical position i
Log.d("JOYSTICKMOVE",event.getHistoricalAxisValue(MotionEvent.AXIS_Y,i)+" "+event.getHistoricalAxisValue(MotionEvent.AXIS_Z,i));
}
// Process current position
Log.d("JOYSTICKMOVE",event.getAxisValue(MotionEvent.AXIS_Y)+" "+event.getAxisValue(MotionEvent.AXIS_Z));
return true;
}
The problem is that when I release all joystick axes, I'm not getting last axes values (0,0) in my log. It stops for example in (0.23,0.11) and appropriate values appear in logcat only after next move event. What's more - situation is the same even if I press normal button (button event is catch by completely other method dispatchKeyEvent(android.view.KeyEvent) )
What's going on ?
You get a MotionEvent.ACTION_MOVE event for the zero position, but the value you receive is not necessarily zero. You need to get the flat range of the joystick, which gives you the values at which we should consider the joystick to be at rest (ie. if we are below the flat range, then we're at the zero position). See getCenteredAxis, which corrects for the flat range (https://developer.android.com/training/game-controllers/controller-input.html):
private static float getCenteredAxis(MotionEvent event,
InputDevice device, int axis, int historyPos) {
final InputDevice.MotionRange range =
device.getMotionRange(axis, event.getSource());
// A joystick at rest does not always report an absolute position of
// (0,0). Use the getFlat() method to determine the range of values
// bounding the joystick axis center.
if (range != null) {
final float flat = range.getFlat();
final float value =
historyPos < 0 ? event.getAxisValue(axis):
event.getHistoricalAxisValue(axis, historyPos);
// Ignore axis values that are within the 'flat' region of the
// joystick axis center.
if (Math.abs(value) > flat) {
return value;
}
}
return 0;
}

Android MotionEvent Pointer Index Confusion

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.

LWJGL: detecting left/right clicks when getButtonCount returns 0

On the Dell Inspiron 15 3000, the touchpad doesn't have any physical left/right buttons. Instead, it is one giant touchpad that is pressure sensitive. I'm assuming it detects right/left clicks based off of hand position on the trackpad.
In my LWJGL application, I detect mouse button clicks with Mouse.isButtonDown(0). This works fine on computers with a mouse with physical buttons, but doesn't work on touchpads that lack physical buttons. Mouse.getButtonCount() returns 0.
Has anybody had any success in detecting if a mouse button is pressed, should the user be using a trackpad that doesn't have physical buttons?
I think, instead of using
org.lwjgl.input.Mouse
This class could be what you are searching for:
org.lwjgl.input.Controllers
http://legacy.lwjgl.org/javadoc/org/lwjgl/input/Controllers.html
Im not entirely sure though since I only have a mouse and no way to test this with a touchpad.
For those who find this in the future, I did find a fix:
You cannot, should there be no physical buttons, use the Mouse.isButtonDown() method. Instead, you are going to have to read the event buffer. To do so, I wrote my own helper class:
public class Mouse{
private static boolean button_left = false, button_right = false;
public static boolean isButtonDown(int button){
if(button == 0) return button_left;
if(button == 1) return button_right;
return false;
}
public static void update(){
while(org.lwjgl.input.Mouse.next()){
if(org.lwjgl.input.Mouse.getEventButton() == 0) button_left = org.lwjgl.input.Mouse.getEventButtonState();
if(org.lwjgl.input.Mouse.getEventButton() == 1) button_right = org.lwjgl.input.Mouse.getEventButtonState();
}
}
}
The update() method is called every tick, and as such I can get button states using Mouse.isButtonDown(button).

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)

selecting textViews and getting their values

I am trying to accomplish following things
Suppose I have 5-6 TextViews with values S N A K E
Now, I want to press S textView then slide my finger over N to select it too and then move my way to E. I want to do that so that i can get "SNAKE" in my string or char sequence etc
If there is any idea do share me. I can't get it how to use onTouch here.
Also, i am adding textViews dynamically so i am setting their ids dynamically too
Best Regards
I've never done this, but here's a thought.
Assuming all your textviews are in some sort of layout, you could disable the focusability on your textview and create an onTouch listener on your underlying container.
Start tracking on Mouse Down events
#Override public boolean onTouch(View v, MotionEvent event) {
if(event.getAction == MotionEvent.ACTION_DOWN)
{
captureInput = true;
}
}
*On your ACTION_MOVE event, check if the current position is above a textview, if so, capture that value*
#Override public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN)
{
captureInput = true;
}
else if(action == MotionEvent.ACTION_MOVE
{
//get x/y of your finger
int X = (int)event.getX();
int Y = (int)event.getY();
//Somehow check if the x,y are overlapping with one of your textivews
// If so, add that textview's text to your list
// Below is pseudo code
/* for(TextView curTV: allTextViews)
{
if(isOverlapping(X,Y))
{
listOfSwipedCharacters.add(curTV.getText());
}
}
}
}
Lastly, when the user releases their finger, stop tracking and do something with the word list
#Override public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN)
{
captureInput = true;
}
else if(action == MotionEvent.ACTION_MOVE
{
//get x/y of your finger
int X = (int)event.getX();
int Y = (int)event.getY();
//Somehow check which textview the X,Y coords are overlapping
// Below is pseudo code
/* for(TextView curTV: allTextViews)
{
if(isOverlapping(X,Y))
{
listOfSwipedCharacters += (curTV.getText());
}
}*/
}
else if(action == MotionEvent.ACTION_UP)
{
captureInput = false;
//Do whatever you want with the string of information that you've captured
}
}
Once again, I have no idea if that will work, it's just a guess at an approach. I suspect performance of cycling through each textview to check for overlaps will be pretty crappy, so there may be a more efficient approach than running a for loop.
Since you know you can't jump over letters when swyping, you could create a short list of all the possible swipe locations (surrounding letters) whenever you add a new letter. Then on your mouse event, check if any of those letters have been hit by the mouse move event. For example, in a standard word search game, once you select a letter, there are only 9 other possible locations for you to swipe (the surrounding letters), so you should only bother checking if those 9 locations have been hit when you capture the mouse move event.
Edit: I suppose you could use the solution above if you attached an onTouch listener to each and every textview. The same premise in regards to touch would apply: Down = start capture, move = capture text, up = stop capture. It would save you from having to identify which textview is being hovered as the textview that fires the onTouch event will be the one that the user is swiping across.

Categories