I am working on a multitouch program that needs to record only the movements made by the second finger or index pointer.
Now the documentation says that we can use MotionEvent.ACTION_POINTER_INDEX_MASK and & it with action and shift by INDEX_SHIFT to get the pointer that made the action like going up or down. But this technique does not work on move.
Is there anyway that we can detect the move action made by a certain pointer alone?
Thx,
yes, you can have something like this in your View class:
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
if(event.getPointerCount()>1){
//where 1 is the index of the second finger
final int Y = event.getY(1);
final int X = event.getX(1);
}
break;
}
}
so depending on what finger you want to get the movent you can set the get to that index. Rember that values may be from 0 (the first pointer that is down) to getPointerCount()-1.
I tested this on 2.2 Gingerbread so I hope it be useful for you :)
You can get the effective pointer index by checking which pointer changed:
private final int MAX_POINTER = 5; // 5 different touch pointers supported on most devices
private float mLastTouchPositionX[];
private float mLastTouchPositionY[];
#Override
public boolean onTouchEvent(MotionEvent aEvent)
int tActionIndex = aEvent.getActionIndex();
int tPointerCount = aEvent.getPointerCount();
/*
* Check which pointer changed on move
*/
if (tMaskedAction == MotionEvent.ACTION_MOVE) {
for (int i = 0; i < tPointerCount && i < MAX_POINTER; i++) {
if (mLastTouchPositionX[i] != aEvent.getX(i) || mLastTouchPositionY[i] != aEvent.getY(i)) {
mLastTouchPositionX[i] = aEvent.getX(i);
mLastTouchPositionY[i] = aEvent.getY(i);
// Found new action index
tActionIndex = i;
break;
}
}
}
...
}
Related
How to show a response while gesture input is in progress?
I'm using swipe down gesture to reload my WebView.
Using this for gesture detection:How to detect swipe direction between left/right and up/down
Problem
SimpleGestureListener captures the result of user input only. It cannot be used to show a response, e.g. animation, while the user is performing a gesture.
Imperfect inelegant solution:
flaw: Shows animation independent of SimpleOnGestureListener; a response to user gesture input is displayed and gesture input may still fail or vice versa.
private volatile float userTouchY = -1;
// translating WebView every onTouch event call is inefficient.
// only translate when translateYBy value is greater than a threshold.
private float translateYBy = 0;
/**
* Shift webView without animation.
* #param num
*/
private void __shiftWebViewDownBy(int num){
float current = webView.getY();
if(current >= webviewTranslationLimit) return;
current += num;
if(current> webviewTranslationLimit) current = webviewTranslationLimit;
webView.setY(current);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
//Skip everything unless at the content top.
if(webView.getScrollY() > 0) return false;
int action = event.getAction();
if(action == MotionEvent.ACTION_MOVE){
float y = event.getY();
float dif = y - userTouchY;
if(dif < webviewTranslationLimit/10){
//dif less than screenHeight*1/40, ignore input
}else if(dif < -(webviewTranslationLimit/5)){
//swipe up
userTouchY = -1;//not swipe down cancelling
__shiftWebViewTopTo(0);
}else if(userTouchY < y) {
//swipe down
translateYBy += dif;
if(userTouchY == -1){
//userTouchY at the initial value, ignore for this once.
userTouchY = y;
}else{
userTouchY = y;
if(translateYBy > 5 ){
__shiftWebViewDownBy((int)translateYBy);
translateYBy = 0;
}
}
}
}else{
Log.d(TAG,"action not move:" +MotionEvent.actionToString(action));
// Webview shift down should only occur while moving
// Once finger is off,cancel
__shiftWebViewTopTo(0);
userTouchY = -1;
}
boolean result = gestureDetector.onTouchEvent(event);
return result;
}
Writing a GestureListener of a sort from scratch obviously solves the problem and I can simply use Toast to message the user for a failed gesture input, but there ought to be an easier, more readable solution.
I have a drag and drop list implementation in Xamarin. The problem is that I want to be able to drag and drop element only when the drag and drop button is touched not the whole element.
I was trying to detect for the ImageView being pressed but I am unable to get the correct hit ImageView.
public bool OnDown (MotionEvent e) {
int x = (int) e.GetX();
int y = (int) e.GetY();
int itemnum = PointToPosition(x, y);
if (itemnum == AdapterView.InvalidPosition) {
return false;
}
if (m_dragging != null) {
m_dragging.Stop();
m_dragging = null;
}
View item = GetChildAt(itemnum - FirstVisiblePosition);
item.Pressed = false;
var dragImage = item.FindViewById<ImageView> (Resource.Id.drag_image);
Rect bounds = new Rect();
dragImage.GetDrawingRect (bounds);
if (!bounds.Contains (x, y)) {
return false;
}
The following code works only for the first element in the list and does not apply to any other. I am suspecting that the detection of hit is incorrect.
Solved followingly. I realized that I actually do not need to care about the Y axis and it's enough to take just X axis in account.
// Detect if the user is actually pressing the drag and drop image or not.
var dragImage = item.FindViewById<ImageView> (Resource.Id.drag_image);
if (dragImage != null) {
var dragImageHitRect = new Rect ();
dragImage.GetDrawingRect (dragImageHitRect);
// In this case we do not care about the Y axis. Just compare the x axis.
if (!dragImageHitRect.Contains (x, (int)dragImage.GetY ())) {
return false;
}
}
We have puzzle game Fruit Dating that is some time already out. Now we got report from one of our players that he can not touch any item in menu. We sent him special version with onscreen debugging. We focused on touches and he returned us this result:
There is every touch event intercepted. I split set of touch events for every single "game touch" in the picure with red lines on the left. Every game touch event is like this: down - (one or more drag) - up. What is strange is that before there is touch up there is always big jump in touch coordinates with touch dragged event.
I think that most of the drag events is just bouncing but there is always at least one that jumps coordinates to lower left corner. Following up event is then with these incorrect coordinates. As we are checking whether the player is touching up in the same area (see area parameter) as he began when touching down then there is never any match and player has feeling that the game is stuck.
The player that reported error has THL W200 phone. We previously tested on lot of different physical devices but never encountered this problem...
Does anyone have some idea why there is motion event (drag) with such a strange coordinates? Did any of you encountered it?
Our game is JNI with thin Java layer. Motionevents are sent to c++ from view like this:
#Override
public boolean onTouchEvent(final MotionEvent aEvent)
{
final int action = aEvent.getAction();
queueEvent(new Runnable()
{
int motionEvent = -1;
public void run()
{
if (action == MotionEvent.ACTION_DOWN)
{
motionEvent = ANDROID_TOUCH_ACTION_DOWN;
}
else if (action == MotionEvent.ACTION_UP)
{
motionEvent = ANDROID_TOUCH_ACTION_UP;
}
else if (action == MotionEvent.ACTION_MOVE)
{
motionEvent = ANDROID_TOUCH_ACTION_MOVE;
}
SBCEngine.engine_on_touch((int) aEvent.getX(), (int) aEvent.getY(), motionEvent);
}
});
return true;
}
Finally found the solution, so if anyone is interested...
The above code is not safe. Before the event is processed the referenced event object may change. The code was taken from book on porting Doom and Quake to Android and you may found similar code at web. It worked on all tested devices until yesterday. It failed on THL W200 device.
I changed the code to this:
#Override
public boolean onTouchEvent(final MotionEvent aEvent)
{
// get masked (not specific to a pointer) action
int maskedAction = aEvent.getActionMasked();
switch (maskedAction)
{
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
{
// get pointer index from the event object
int pointerIndex = aEvent.getActionIndex();
// get pointer ID
int pointerId = aEvent.getPointerId(pointerIndex);
//Log.d("POINTER", "pointer down: ID = " + pointerId + " x=" + aEvent.getX(pointerIndex) + ", y = " + aEvent.getY(pointerIndex));
queueEvent(new MotionEventRunnable(pointerId, maskedAction,
(int) aEvent.getX(pointerIndex),
(int) aEvent.getY(pointerIndex)));
break;
}
case MotionEvent.ACTION_MOVE:
{
// a pointer was moved
int size = aEvent.getPointerCount();
for (int i = 0; i < size; i++)
{
// Get the pointer ID and index
int pointerId = aEvent.getPointerId(i);
int pointerIndex = aEvent.findPointerIndex(pointerId);
//Log.d("POINTER", "pointer move: ID = " + pointerId + " x=" + aEvent.getX(pointerIndex) + ", y = " + aEvent.getY(pointerIndex));
queueEvent(new MotionEventRunnable(pointerId, maskedAction,
(int) aEvent.getX(pointerIndex),
(int) aEvent.getY(pointerIndex)));
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
{
// get pointer index from the event object
int pointerIndex = aEvent.getActionIndex();
// get pointer ID
int pointerId = aEvent.getPointerId(pointerIndex);
//Log.d("POINTER", "pointer up: ID = " + pointerId + " x=" + aEvent.getX(pointerIndex) + ", y = " + aEvent.getY(pointerIndex));
queueEvent(new MotionEventRunnable(pointerId, maskedAction,
(int) aEvent.getX(pointerIndex),
(int) aEvent.getY(pointerIndex)));
break;
}
}
return true;
}
with MotionEventRunnable class like this:
class MotionEventRunnable implements Runnable
{
private int mPointerID;
private int mActionID;
private int mX, mY;
//-----------------------------------------------------
public MotionEventRunnable(int aPointerID, int aActionID, int aX, int aY)
{
mPointerID = aPointerID;
mActionID = aActionID;
mX = aX;
mY = aY;
}
//-----------------------------------------------------
#Override
public void run()
{
// send to native
SBCEngine.engine_on_touch(mPointerID, mX, mY, mActionID);
}
}
The code is more complex as it also handles multitouch but the point is that I cache all the required values: pointer id, action, x and y. It solved my problems.
After a lot of tinkering, I think I've finally come up with a good multitouch handling system for my android game. It makes use of Robert Greene's Input Pipeline, modified for use with multitouch. Right now, the code has a simple system that records which pointer ID is currently doing which action (right now just shooting and moving). Each pointer's state is kept in a Pointer class, which is just a simple encapsulation of whether it is down, and it's coordinates. The ID acts as the pointer array index.
This seems like it should work well in practice, but in game it behaves very erratic. When recording the pointer actions in LogCat, oftentimes Android will send an "UP" action when the pointer remains down, or just before a number of "MOVE" actions by the pointer. Because my code believes the pointer is up, the game doesn't respond to it.
This also happens with button presses like the shooting button. When the pointer comes down on the area (which right now is just simply the lower left region), Android will send multiple "UP" and "DOWN" actions even though the pointer remains down the whole time. I had a single touch movement system before and none of these problems happened.
Is this just an issue with how I am reacting to the events? Should I handle POINTER_DOWN and DOWN separately? Or should I detect which pointers are moving after the "UP" action to see which ones really are down despite what Android says?
Here's my current code in my thread which receives the input events from Android. Because it is a pipeline system, I have the events encapsulated in the InputObject which is somewhat similar to Robert Greene's. Maybe a new set of eyes can help me tell what's wrong? Thanks for any help!
private int inx, iny;
private int shootID;
public boolean shooting = false;
private int moveID;
public boolean moveDown = false;
private static final int MAX_POINTERS = 10;
private Pointer[] pointers = new Pointer[MAX_POINTERS];
public void inputTouch(InputObject in) {
switch(in.action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
pointers[in.pID].press(in.pX[in.actionIndex], in.pY[in.actionIndex]);
//Log.i("D", "DOWN");
break;
case MotionEvent.ACTION_MOVE:
for(int p = 0; p < in.pointCount; p++) {
int id = in.pointerIDs[p];
pointers[id].setCoord(in.pX[id], in.pY[id]);
}
//Log.i("D", "MOVE");
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
pointers[in.pID].release();
if(shootID == in.pID) {
shooting = false;
}
if(moveID == in.pID) {
moveDown = false;
}
//Log.i("D", "UP");
break;
case MotionEvent.ACTION_CANCEL:
default:
break;
}
for(int ap = 0; ap < MAX_POINTERS; ap++) {
if(pointers[ap].down) {
if(pointers[ap].x < world.cam.pixelWidth / 4 &&
pointers[ap].y > world.cam.pixelHeight - (world.cam.pixelHeight / 4)) {
shootID = ap;
shooting = true;
} else {
inx = pointers[ap].x;
iny = pointers[ap].y;
moveID = ap;
moveDown = true;
}
}
}
StringBuilder sb = new StringBuilder();
for(int j = 0; j < 3; j++) {
sb.append("ID " + (j+1) + ": " + pointers[j].down + "[" + pointers[j].x + ", " + pointers[j].y + "]" + " | ");
}
//Log.i("D", sb.toString());
}
Hope you got the answer by now, but here''s my solution:
First of all, there's a couple of things to add in the InputHolder class:
-internal public fields for mPointerIndex = event.getPointerIndex and mPointerID = event.getPointerID(mPointerIndex) (these get assigned in the useEvent/useHistory)
-if you only need to track 2 touchpoints, you need to add mPointerIndex2, x2 and y2 aswell. Add more as you need to track more points.
-add definitions for the case where MotionEvent.ACTION_POINTER_2_UP/DOWN gets passed. Turns out, POINTER_1 is the same as POINTER_UP/DOWN! This really tripped me up because I was falling through the switch case to the default. I only caught this because I changed my default to -1 and saw it logged.
Depending on how you process the actions in your processInput(obj), you map these to different ints. In my case I used the obj.y to see if they where left/right touches and I only needed two points, so I mapped these to ACTION_TOUCH_POINTER_UP/DOWN instead of giving each touch it's own action int identifier.
-now, if you want to track multiple touch points, you would have to do the above and the below in a for loop over all entries in event.getPointerCount(). In my case I was only interested in the x/y of one other touchpoint, so I could get away with only doing a check after I had filled the first point and the other pointerindex was easy to deduce:
public void useEvent(MotionEvent event) {
eventType = EVENT_TYPE_TOUCH;
int a = event.getAction();
switch (a) {
case MotionEvent.ACTION_DOWN:
action = ACTION_TOUCH_DOWN;
break;
case MotionEvent.ACTION_POINTER_DOWN:
action = ACTION_TOUCH_POINTER_DOWN;
break;
case MotionEvent.ACTION_POINTER_2_DOWN:
action = ACTION_TOUCH_POINTER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
action = ACTION_TOUCH_MOVE;
break;
case MotionEvent.ACTION_UP:
action = ACTION_TOUCH_UP;
break;
case MotionEvent.ACTION_POINTER_UP:
action = ACTION_TOUCH_POINTER_UP;
break;
case MotionEvent.ACTION_POINTER_2_UP:
action = ACTION_TOUCH_POINTER_UP;
break;
default:
action = -1;
}
time = event.getEventTime();
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
pointerID = event.getPointerId(pointerIndex);
x = (int) event.getX(pointerIndex);
y = (int) event.getY(pointerIndex);
if (event.getPointerCount() > 1)
{
pointerIndex2 = pointerIndex== 0 ? 1 : 0;
x2 = (int)event.getX(pointerIndex2);
y2 = (int)event.getY(pointerIndex2);
}
}
If you''re tracking more points, or need more information about each touchevent, you have to extend the for loop up over the action switch case.
Anyway, now you need two variables in your thread so you can keep track of your two touches: call 'em touchOneID and touchTwoID (or index). On an ACTION_POINTER_2_UP, the obj.mPointerID refers to the obj which is going UP! This means that the other touch will change it's ID! Keep track of this change, and you're sorted. The internal obj's mPointerID/Index will always be correct, you just have to track them correctly in your surfaceview's thread so you can act accordingly to when you get a POINTER_DOWN. In my case, I did a simple x position check to determine what to do in my ACTION_MOVE event which told me enough to correctly determine which x/y or x2/y2 I should use and how to use it. This meant less code to run and limits to the things I had to keep in memory, but it all depends on what and how much information you need.
Finally:
To be honest, if you handled the definitions correctly and assigned every MotionEvent and held them in the InputObject, you'd probably be fine. Hell, I think you can ignore and lose the whole switch case and just say obj.mAction = event.getAction() and handle these in your processInput(obj)!
Meaning, all those static ints he's remapping to in the InputHolder seem unnecessary, unless you really only need one or two touch definitions (which explains his mysterious standalone comment of "this app is only interested in down touches"). Getting rid of those statically defined ints also means you can just test against MotionEvent.ACTION_CODE instead of doing a lookup against InputHolder.TOUCH_ACTION_CODE.
Can anyone help please.
Im writing a small Android game where the player is able to select a "barrier" and drag it accross the screen with their finger. I have the barriers drawn on the screen and I am able to drag it accross the screen.
My problem however is when I add more than 1 barrier, eg 3 barriers, and drag a barrier accross the screen, they all drag and they all drag to the same position. That is to say they all lie on top of each other, making it look as though there is only 1 barrier.
Here is my code, can anyone please tell me where I am going wrong/explain where I am going wrong.
public class MainGamePanel extends SurfaceView implements SurfaceHolder.Callback, SensorEventListener {
// Initialising the Barrier
private Barrier barrier[] = new Barrier[3];
// The Main Game Panel
public MainGamePanel(Context context) {
super(context);
// Adding a call-back (this) to the surfaceHolder to intercept events
getHolder().addCallback(this);
// Creating the Game items
// The starting coordinates of the Barrier
int x = 30;
int y = 270;
barrier[0] = new Barrier(BitmapFactory.decodeResource(getResources(), R.drawable.blue_barrier), x, y);
barrier[1] = new Barrier(BitmapFactory.decodeResource(getResources(), R.drawable.green_barrier), x + 15, y);
barrier[2] = new Barrier(BitmapFactory.decodeResource(getResources(), R.drawable.pink_barrier), x + 30, y);
// Create the Game Loop Thread
thread = new MainThread(getHolder(), this);
// Make the GamePanel focusable so it can handle events
setFocusable(true);
}
// Handles the touch events
public boolean onTouchEvent(MotionEvent event)
{
int eventAction = event.getAction();
int x = (int)event.getX();
int y = (int)event.getY();
switch (eventAction)
{
// Touch down so check if finger is on Barrier
case MotionEvent.ACTION_DOWN:
if (x > barrier[0].getX() && x < barrier[0].getX() + 8
&& y > barrier[0].getX() && y < barrier[0].getY() + 8)
{
barrier[0].isTouched();
}
else if (x > barrier[1].getX() && x < barrier[1].getX() + 8
&& y > barrier[1].getX() && y < barrier[1].getY() + 8)
{
barrier[1].isTouched();
}
else if (x > barrier[2].getX() && x < barrier[2].getX() + 8
&& y > barrier[2].getX() && y < barrier[2].getY() + 8)
{
barrier[2].isTouched();
}
break;
// Touch-drag with the Barrier
case MotionEvent.ACTION_MOVE:
// Move the Barrier the same as the finger
for (int i = 0; i < barrier.length; i++)
{
if (barrier[i] == barrier[0])
{
barrier[0].setX(x);
barrier[0].setY(y);
} // end if
else if (barrier[i] == barrier[1])
{
barrier[1].setX(x);
barrier[1].setY(y);
}
else if (barrier[i] == barrier[2])
{
barrier[2].setX(x);
barrier[2].setY(y);
} // end else if
} // end for
break;
case MotionEvent.ACTION_UP:
// Finger no longer on Barrier - Do Nothing
break;
}
return true;
}
// Render - Draws the Game Item Bitmaps to the screen
public void render(Canvas canvas)
{
// Set the background to white
canvas.drawColor(Color.WHITE);
barrier[0].draw(canvas);
barrier[1].draw(canvas);
barrier[2].draw(canvas);
}
// Update
// This is the Game's update method
// It iterates through all the Objects and calls their update() methods (if they have one)
public void update()
{
} // end update
In your barrier move code, you aren't checking a particular barrier is touched, so you're moving all of them to the same coordinates. The following loop does exactly the same thing as your current code:
// Move the Barrier the same as the finger
for (int i = 0; i < barrier.length; i++)
{
barrier[i].setX(x);
barrier[i].setY(y);
} //end for
To fix this, you need to check if the current barrier in the loop is the one that was touched, and can change that whole loop to something like:
// Move the Barrier the same as the finger
for (int i = 0; i < barrier.length; i++)
{
if (barrier[i].isTouched())
{
barrier[i].setX(x);
barrier[i].setY(y);
} // end if
} // end for
You would then need to make sure that you un-set the touched property in the ACTION_UP section. If you post your Barrier class definition I can probably help more.
I'm pretty sure your problem could be solved by making the barriers three separate objects instead of in an array.
As already pointed out, your code in handling the move is wrong because it moves all the Barriers to the same X,Y when it should only move the one that is touched.
Also, you are never resetting the isTouched on the objects in the Action up. When the user lifts their finger you should set them all to isTouched == false. If you don't do that then once you touch one it will always move to the X,Y.