i am a student developing an air hockey android games.
i am having a problem with understanding multi touch.
i just learns about ACTION_DOWN, ACTION_POINTER_DOWN etc.
but by problem is at ACTION_MOVE.
i create 2 sprite for 2 player.1st sprite will move where my 1st finger go, but my 2nd sprite doesn't move where my 2nd finger move.
my question is, how i want to identified which finger is moving in ACTION_MOVE? i have tried to use getPointerId(index), but i am not understand how to use it because the index is changing if the 1st finger leave the screen
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
break;
}
case MotionEvent.ACTION_UP: {
break;
}
case MotionEvent.ACTION_POINTER_UP: {
break;
}
case MotionEvent.ACTION_MOVE: {
if((int)event.getPointerId(index) == 0){ //i know this IF statement is wrong, what should i do?
player1.setX((int)event.getX()); //player1 & player2 is a sprite object
player1.setY((int)event.getY());
}
if((int)event.getPointerId(index) == 1){
player1.setX((int)event.getX());
player1.setY((int)event.getY());
}
}
}
You cannot know which area will be touched first. The first finger/ touch gets pointer id = 0 the second = 1 and so on. What you can do is this:
public boolean onTouchEvent(MotionEvent event) {
// If this does not work search for a way to get the screen width
WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int eventaction = event.getActionMasked();
int num = event.getPointerCount();
// For every touch
for (int a = 0; a < num; a++) {
int X = (int) event.getX(event.getPointerId(a));
int Y = (int) event.getY(event.getPointerId(a));
int allowed_touch_range = display.getWidth() / 2; // Your screen width divided by 2
switch (eventaction) {
case MotionEvent.ACTION_MOVE:
// Left half of the screen
if (X < allowed_touch_range) {
/* Player1 Move */
}
// Rigth half
if (X > allowed_touch_range) {
/* Player2 Move */
}
break;
}
}
return true;
}
Related
i have problem with onTouch. I have GridLayout where i have 3x3 buttons like a image below.
B1,B2 -- is the Button view. My problem is, when i put 3 fingers on the screen at the same time and move all of them down then my phone detect it like threeSwipeGesture and make screenshot. How to avoid phone to detect gesture?
TIP: When i put 3 fingers one by one (tap 1 - wait second - tap 2 - wait second - tap 3) then i can move my fingers and gesture is not detected.
#Override
public boolean onTouch(View view, MotionEvent event) {
int action = event.getActionMasked();
int index = event.getActionIndex();
int x = -1;
int y = -1;
switch (action) {
case MotionEvent.ACTION_MOVE:
return false;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_CANCEL:
gridLayout.setBackgroundColor(Color.parseColor("#df4930"));
set.clear();
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
x = (int) event.getX(index);
y = (int) event.getY(index);
for (int i = 0; i < gridLayout.getChildCount(); i++) {
View button = gridLayout.getChildAt(i);
Rect outRect = new Rect(button.getLeft(), button.getTop(), button.getRight(), button.getBottom());
if (outRect.contains(x, y)) {
set.add(button.getTag());
}
}
if (set.size() == 4) {
Log.d(DEBUG_TAG, set.toString());
gridLayout.setBackgroundColor(Color.parseColor("#ffc120"));
}
}
return true;
}
Here is error log in the console when gesture is detected.
D/ViewRootImpl: cancle motionEvent because of threeGesture detecting
After doing some tutorials on touch events and drawing on a canvas I tried to combine what I learned and make an application that creates a rectangle using 2 touch points. I am able to drag and create a rectangle using a single touch point but as soon as I use a second finger the rectangle starts behaving weirdly, either not drawing the rectangle at all or disappearing after dragging it a bit. Furthermore, if for example I draw a rectangle by having one touch point being on the lower left corner and the other one on the upper right corner of the screen the rectangle disappears if I drag my fingers to the point where they cross each other.
gif of the application running
private float xDown = 0,yDown = 0, xUp = 0, yUp = 0;
boolean touched = false;
#Override
protected void onDraw (Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
if(touched) {
canvas.drawRect(xDown, yDown, xUp, yUp, mPaint);
}
}
#Override
public boolean onTouchEvent (MotionEvent event) {
int fingers = event.getPointerCount();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if (fingers == 1) {
xDown = event.getX(0);
yDown = event.getY(0);
xUp = 0;
yUp = 0;
}
if (fingers == 2) {
xUp = event.getX(1);
yUp = event.getY(1);
xDown = event.getX(0);
yDown = event.getY(0);
touched = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (fingers == 1) {
xUp = event.getX();
yUp = event.getY();
touched = true;
}
if (fingers == 2) {
xUp = event.getX(1);
yUp = event.getY(1);
xDown = event.getX(0);
yDown = event.getY(0);
touched = true;
}
break;
case MotionEvent.ACTION_UP:
if (fingers == 1) {
xUp = event.getX();
yUp = event.getY();
touched = true;
}
if (fingers == 2) {
xUp = event.getX(1);
yUp = event.getY(1);
xDown = event.getX(0);
yDown = event.getY(0);
touched = true;
}
break;
}
invalidate();
return true;
MotionEvent.ACTION_DOWN will only happen with the FIRST touch event to the screen.
"ACTION_DOWN
Constant for getActionMasked(): A pressed gesture has started..." (From https://developer.android.com/reference/android/view/MotionEvent#ACTION_DOWN)
For your SECOND or more fingers, you need to check for ACTION_POINTER_DOWN
"ACTION_POINTER_DOWN
Constant for getActionMasked(): A non-primary pointer has gone down..." (From https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_DOWN)
Because you are using only two fingers, you won't need to worry about the ActionIndex ( getActionIndex() ). The first finger touching the screen is always an ACTION_DOWN and every touch down after is an ACTION_POINTER_DOWN.
On the ACTION_UP it is the reverse. Every touch up (except the last touch) is ACTION_POINTER_UP and the last touch up is an ACTION_UP.
NOTE: The first touch (ACTION_DOWN) always has and index of 0.
The index stays assigned to the touch until the touch is removed from the screen. Because you are hard-coding the indexes, any accidental touches to the screen will cause some unexpected results. For a test program, this is fine but you will need to handle the indexes eventually.
Example:
Finger 1 (ACTION_DOWN) touches the screen and gets an index of 0.
Finger 2 (ACTION_POINTER_DOWN) touches the screen and gets an index of 1.
Finger 3 (ACTION_POINTER_DOWN) touches the screen and gets an index of 2
Finger 2 (index 1) is lifted off the screen (ACTION_POINTER_UP)
Finger 1 (index 0) and finger 3 (index 2) are still in the MotionEvent and their indexes remain assigned to them.
The simplest change for you to make is Change:
case MotionEvent.ACTION_DOWN:
if (fingers == 1) {
...
to:
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (fingers == 1) {
...
and also Change:
case MotionEvent.ACTION_UP:
if (fingers == 1) {
...
to:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (fingers == 1) {
...
because you are already handling the finger count & indexes.
This change will make you code "more correct" because it will capture the second DOWN and the first UP but it will not fix your issue. ACTION_MOVE is already catching both touch points when they move and you are already handling the two indexes and assigning the X's & Y's. So the issue has to be elsewhere in you program.
I'm using motion event, action_down, action_move etc, to detect when there is no finger movement but the finger is still on the screen. For instance, the user moves in a vertical line from top of the screen to the bottom and then stops movement without lifting the finger. How do I detect when there is no movement but the finger is still on the screen after the drag/swipe?
EDIT: What I'm trying to do is count every time I change direction of movement in a vertical direction. And to do that I'm trying to detect when I stop movement against the change of movement. For instance, I move down the screen and then move back up, that counts as two counts. Here is my code, please don't provide me with code as a direct answer but hints or clues so that I can try and figure it out myself (my code might look a bit confusing, I'm just trying out different things):
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
oldX = event.getX();
oldY = event.getY();
oldSpeedY = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
posY = event.getY();
posSpeedY = System.currentTimeMillis();
float timeElapsed = (posSpeedY - oldSpeedY) / 1000;
float diffSpeed = posSpeedY - oldSpeedY;
if (changeOfMovement(posY, oldY)) {
//if (diffSpeed == 0)
count += 1;
}
break;
case MotionEvent.ACTION_UP:
// count seconds here
break;
}
Toast.makeText(getApplicationContext(), "Swipe: " + count,
Toast.LENGTH_SHORT).show();
return false;
}
public boolean changeOfMovement(float posY, float oldY) {
int newY = Math.round(posY);
double distance = Math.abs(newY - oldY);
oldY = newY;
//float speed = (float) (distance / time);
//if (distance < 25)
//return false;
//if (speed == 0)
//return true;
if (distance < 25)
return true;
return false;
}
The finger touches the screen until you receive either of the MotionEvent actions ACTION_UP or ACTION_CANCEL
The ACTION_DOWN Event is still valid until the finger is lifted from the screen or you can wait till a ACTION_UP Event is detected
I'm not sure if my situation is like yours, but mine I was want to detect if the user stopped moving inside circle within one second and starts moving again, by comparing between last two currentTimeMillis.
So, what I've done is I initialized fixed ArrayList to save the last two times in move event:
public class FixedArrayList extends ArrayList {
int fixedSize = 10;
public FixedArrayList(){}
public FixedArrayList(int fixedSize){
this.fixedSize = fixedSize;
}
#SuppressWarnings("All")
public void addItem(Object object){
if(size() < fixedSize){
add(object);
} else{
remove(0);
add(object);
}
}
}
Now, I've initialized my new class with fixed 2 items to be saved:
FixedArrayList savedLastMove = new FixedArrayList(2);
int secondsToWait = 1;
public boolean onTouchEvent(MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case (MotionEvent.ACTION_MOVE):
currentSystemTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
savedLastMove.addItem(currentSystemTime);
if(savedLastMove.size() >= 2 && ((long)savedLastMove.get(1) - (long)savedLastMove.get(0)) >= secondsToWait){
//Do what you want after secondsToWait
}
return true;
}
I hope that will help! Because it solved my problem.
In my multitouch program I'm encountering a runtime error where the Multitouch logic for a pointer sliding on the screen (MotionEvent.ACTION_MOVE) is only entered by the initial pointer. This means the code within my switch statement for case ACTION_MOVE is only accessible by the first finger to touch the screen, aka pointer index = 0. The following is the relevant parts of my code:
public boolean onTouchEvent(MotionEvent event) {
// get pointer index from the event object
int pointerIndex = event.getActionIndex();
// get pointer ID
int pointerId = event.getPointerId(pointerIndex);
// get masked (not specific to a pointer) action
int maskedAction = event.getActionMasked();
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
// We have a new pointer. Lets add it to the list of pointers
PointF f = new PointF();
f.x = event.getX(pointerIndex);
f.y = event.getY(pointerIndex);
mActivePointers.put(pointerId, f);
Log.d("Multitouch", "x coord = " + Float.toString(f.x));
Log.d("Multitouch", "y coord = " + Float.toString(f.y));
break;
}
case MotionEvent.ACTION_MOVE: { // a pointer was moved
Log.e("Multitouch", "Pointer"+ Integer.toString(pointerId) +" is moving");
for (int size = event.getPointerCount(), i = 0; i < size; i++) {
PointF f = mActivePointers.get(event.getPointerId(i));
if (f != null) {
f.x = event.getX(i);
f.y = event.getY(i);
}
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mActivePointers.remove(pointerId);
break;
}
}
invalidate();
return true;
}
Needless to say I want the code in Action_Move to be triggered by any pointer, not just the initial pointer. What is preventing pointers beyond index zero from triggering a move event?
As stated in the Documentation, MotionEvent.getActionIndex() only returns the Index for ACTION_POINTER_UP and ACTION_POINTER_DOWN (http://developer.android.com/reference/android/view/MotionEvent.html#getActionIndex%28%29). You however use the pointer ID generated from the pointer Index returned by said function in your log output. Hence, the log is incorrect, as the function doesn't return what you expect.
I found out in one of my earlier questions that setting up specific x,y coordinates as Touch Listeners was too specific and that just one position would be too small to pick up a touch input.
#ligi mentioned Regions in a comment under my question. I looked into it and realised that it's what I need, I just can't find out how to implement one.
Here is the code for a TouchListener for a specific x,y coordinate:
public boolean onTouchEvent(MotionEvent ev) {
if (!mDragging) {
return false;
}
int x = (int) ev.getX();
int y = (int) ev.getY();
if (x == 100 && y == 200) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "ACTION DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "ACTION MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "ACTION UP");
break;
}
}
Does anyone know how to set up a Region instead? Say if a user finger moves into the region area, do something. I can't find any basic implementations anywhere else.
Here's an image if the desc. wasn't clear:
You could do something like this
public boolean onTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "ACTION DOWN OR MOVE");
// check if the event is in region 1
if(x >= "left_x_region1" && x <= "right_x_region1"
&& y >= "top_x_region1" && y <= "bottom_x_region1")
// do something for region1
// check if the event is in region 2
else if(x >= "left_x_region2" && x <= "right_x_region2"
&& y >= "top_x_region2" && y <= "bottom_x_region2")
// do something for region1
// continue for other cases
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "ACTION UP");
break;
}
}
Or you could simplify it even further by making a list of what those actual values are, or even better create Rects from those values
List<Rect> regions = new ArrayList<Rect>();
regions.add(new Rect("left_x_region1", "top_y_region1", "right_x_region1", "bottom_y_region1");
regions.add(new Rect("left_x_region2", "top_y_region2", "right_x_region2", "bottom_y_region2");
// continue for each region
and then check your event in those regions inside your ACTION_DOWN || ACTION_MOVE section of the switch statement
for(int i = 0; i < regions.size(); i++){
if(regions.get(i).contains(x, y)){
// do stuff for region i
return;
}
}
What this does is checks to see if the motion event occurred within the bounds of the region.
I would imagine you'd use a rect in Android. You'd specify the rectangle(s) and see if the rect(s) contains your value. In the example below I made a list of rectangles so there are multiple hotspots. You could ideally just use one if you want just one specific rectangular region. The .contains internal method allows you to specify regions which your x,y coordinates would potentially entail.
List<Rect> retangles;
...
for(Rect rect : rectangles){
if(rect.contains(x,y)){
System.out.println("Touched Rectangle, do what you need to do.");
}
}
or for just one
Rect rect = new Rect(0, 0, 200, 100);
if(rect.contains(x,y))
System.out.println("Touched Rectangle, do what you need to do.");