Android multitouch: no ACTION_POINTER_DOWN and UP events - android

Here is my code:
public boolean onTouch(View v, MotionEvent event) {
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
Log.d("getButtonCode", "catch ACTION_DOWN");
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.d("getButtonCode", "catch ACTION_POINTER_DOWN");
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d("getButtonCode", "catch ACTION_POINTER_UP");
break;
case MotionEvent.ACTION_UP:
Log.d("getButtonCode", "catch ACTION_UP");
case MotionEvent.ACTION_CANCEL:
Log.d("getButtonCode", "catch ACTION_CANCEL");
case MotionEvent.ACTION_MOVE:
break;
default:
break;
}
return true;
}
and I never catch ACTION_POINTER_DOWN and ACTION_POINTER_UP. If I remove breakfrom ACTION_DOWN, I catch ACTION_POINTER_DOWN always after ACTION_DOWN, even if it is first pointer. I think it should be so: first pointer catch only ACTION_DOWN, every next pointer only ACTION_POINTER_DOWN, if non-primary pointer has gone up, I should catch ACTION_POINTER_UP.
But it doesn't work. What is wrong in my code?
PS: I saw other similar questions, but no answer helped me.

Instead of using a switch statement, I flowed through a series of if statements. This let me test against event.getAction() or event.getActionMasked() and it allowed multiple possible actions through at once.
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
startingX = (int) event.getX();
startingY = (int) event.getY();
}
if(event.getAction() == MotionEvent.ACTION_MOVE) {
int scaleX = 1;
int scaleY = 1;
if(touchStateScale == true){
scaleX = (int) (event.getX(0) - event.getX(1));
scaleY = (int) (event.getY(0) - event.getY(1));
}
int endingX = (int) event.getX();
int endingY = (int) event.getY();
int newX = endingX-startingX;
int newY = endingY-startingY;
((ImageView) v).setImageBitmap(drawNewImageScaled(baseImage, newImageResourceId, newX, newY, scaleX, scaleY));
}
if(event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN){
Log.i(TAG, "second pointer detected");
touchStateScale = true;
}
if (event.getAction() == MotionEvent.ACTION_UP) {
touchStateScale = false;
return false;
}
return true;
}
};

Related

How can get Touch of 2 Fingers at a Time?

I click 2 fingers on a SurfaceView at a Time, i want get X,Y of 2 Points:
This is my code, but it only get X,Y of 1 Finger.
How can get X,Y of 2 Fingers at a Time?
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
int ipoiter = event.getPointerCount();
Toast.makeText(getContext(), String.valueOf(ipoiter), Toast.LENGTH_SHORT).show();
for(int i=0;i<ipoiter;i++){
int x= (int)event.getX(i);
int y = (int)event.getY(i);
Toast.makeText(getContext(), String.valueOf(x), Toast.LENGTH_SHORT).show();
}
}
}
I had try below code but action=0, so can't process.
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
Toast.makeText(getContext(), String.valueOf(action), Toast.LENGTH_SHORT).show();
if (action == MotionEvent.ACTION_POINTER_DOWN) {
int ipoiter = event.getPointerCount();
Toast.makeText(getContext(), String.valueOf(ipoiter), Toast.LENGTH_SHORT).show();
for(int i=0;i<ipoiter;i++){
int x= (int)event.getX(i);
int y = (int)event.getY(i);
Toast.makeText(getContext(), String.valueOf(x), Toast.LENGTH_SHORT).show();
}
}
}
ACTION_POINTER_DOWN is triggered for each newly touched point on screen.
to get the all touch points try something as follows.
You will get the active finger points in mActivePointers
private SparseArray<PointF> mActivePointers = new SparseArray<PointF>();
#Override
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);
break;
}
case MotionEvent.ACTION_MOVE: { // a pointer was moved
for (int size = event.getPointerCount(), i = 0; i < size; i++) {
PointF point = mActivePointers.get(event.getPointerId(i));
if (point != null) {
point.x = event.getX(i);
point.y = event.getY(i);
}
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: {
mActivePointers.remove(pointerId);
break;
}
}
invalidate();
return true;
}
Source : Check this

Gesture- volume control

i am trying to implement volume control using onTouchListener.
which i did nicely, but the problem is the volume gets increased by just 1 step.
For example: Total volume is 100 and steps are 10,20,30,40.. so on, it will only increase it 10 --NO MATTER HOW MANY TIMES I SWIPE THE LAYOUT UP--
but the volume down is super INSTANT, slight touch --GOES TO ZERO LIKE BANANAS--
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP)
Log.i(TAG, "Action :" + event.getAction() + "\t X :" + event.getRawX() + "\t Y :"+ event.getRawY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = event.getY();
case MotionEvent.ACTION_UP:
downY = event.getY();
case MotionEvent.ACTION_MOVE:
float y2 = event.getY();
diffY = (long) (Math.ceil(event.getY() - downY));
//if (Math.abs(diffY) > Math.abs(diffX)) {
if (downY < y2) {
//down swipe volume decrease
// audioManager.adjustVolume(AudioManager.ADJUST_LOWER, AudioManager.FLAG_PLAY_SOUND);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_PLAY_SOUND);
} else if (downY > y2) {
//up swipe volume increase
// audioManager.adjustVolume(AudioManager.ADJUST_RAISE, AudioManager.FLAG_PLAY_SOUND);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FLAG_PLAY_SOUND);
}
// }
}
return true;
}
I fixed it changing setStreamVolume to adjustStreamVolume.

Touch movement in only four directions?

I'm doing a simple match-three game, similar to Bejeweled and I just want to move sprite objects by touching the sprite object and then move it one step in four directions like left, right, up and down. I do this by comparing the X and Y values on Down with the X and Y values on Move. It's working but it's far from perfect! It's so easy to get a wrong value if the movement isn't straight. My questions is: is there a way to improve this and make it better?
I have also looked at gesture, but this seems very complicated to use with a surfaceview that I have.
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("test","Down");
touchActionDownX = (int)event.getX();
touchActionDownY = (int)event.getY();
touchActionMoveStatus = true;
gameLoop.touchX = (int)event.getX();
gameLoop.touchY = (int)event.getY();
gameLoop.touchActionDown = true;
break;
case MotionEvent.ACTION_POINTER_UP:
touchActionMoveStatus = true;
break;
case MotionEvent.ACTION_MOVE:
//Log.i("test","Move");
gameLoop.touchActionMove = true;
if(touchActionMoveStatus) {
touchActionMoveX = (int)event.getX();
touchActionMoveY = (int)event.getY();
if(touchActionMoveX < touchActionDownX)
Log.i("test","Move Left");
else if(touchActionMoveX > touchActionDownX)
Log.i("test","Move Right");
else if(touchActionMoveY < touchActionDownY)
Log.i("test","Move Up");
else if(touchActionMoveY > touchActionDownY)
Log.i("test","Move Down");
touchActionMoveStatus = false; // Will be set to true when pointer is up
}
break;
}
// return false;
return true; // This gets the coordinates all the time
}
Try something like this:
#Override
public boolean onTouch(View v, MotionEvent event) {
//You may have to play with the value and make it density dependant.
int threshold = 10;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("test","Down");
touchActionDownX = (int)event.getX();
touchActionDownY = (int)event.getY();
touchActionMoveStatus = true;
gameLoop.touchX = (int)event.getX();
gameLoop.touchY = (int)event.getY();
gameLoop.touchActionDown = true;
break;
case MotionEvent.ACTION_POINTER_UP:
touchActionMoveStatus = false;
break;
case MotionEvent.ACTION_MOVE:
//Log.i("test","Move");
gameLoop.touchActionMove = true;
if(touchActionMoveStatus) {
touchActionMoveX = (int)event.getX();
touchActionMoveY = (int)event.getY();
if(touchActionMoveX < (touchActionDownX - threshold) && (touchActionMoveY > (touchActionDownY - threshold)) && (touchActionMoveY (touchActionDownY + threshold))){
Log.i("test","Move Left");//If the move left was greater than the threshold and not greater than the threshold up or down
touchActionMoveStatus = false;
}
else if(touchActionMoveX > (touchActionDownX + threshold) && (touchActionMoveY > (touchActionDownY - threshold)) && (touchActionMoveY < (touchActionDownY + threshold))){
Log.i("test","Move Right");//If the move right was greater than the threshold and not greater than the threshold up or
touchActionMoveStatus = false;
}
else if(touchActionMoveY < (touchActionDownY - threshold) && (touchActionMoveX > (touchActionDownX - threshold)) && (touchActionMoveX < (touchActionDownX + threshold))){
Log.i("test","Move Up");//If the move up was greater than the threshold and not greater than the threshold left or right
touchActionMoveStatus = false;
}
else if(touchActionMoveY > (touchActionDownY + threshold) && (touchActionMoveX > (touchActionDownX - threshold)) && (touchActionMoveX < (touchActionDownX + threshold))){
Log.i("test","Move Down");//If the move down was greater than the threshold and not greater than the threshold left or right
touchActionMoveStatus = false;
}
}
break;
}
// return false;
return true; // This gets the coordinates all the time
}
Or use a ratio:
#Override
public boolean onTouch(View v, MotionEvent event) {
//You may have to play with the value.
//A value of two means you require the user to move twice as
//far in the direction they intend to move than any perpendicular direction.
float threshold = 2.0;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("test","Down");
touchActionDownX = (int)event.getX();
touchActionDownY = (int)event.getY();
touchActionMoveStatus = true;
gameLoop.touchX = (int)event.getX();
gameLoop.touchY = (int)event.getY();
gameLoop.touchActionDown = true;
break;
case MotionEvent.ACTION_POINTER_UP:
touchActionMoveStatus = true;
break;
case MotionEvent.ACTION_MOVE:
//Log.i("test","Move");
gameLoop.touchActionMove = true;
if(touchActionMoveStatus) {
touchActionMoveX = (int)event.getX();
touchActionMoveY = (int)event.getY();
// I haven't tested this so you may have a few typos to correct.
float ratioLeftRight = Math.abs(touchActionMoveX - touchActionDownX)/Math.abs(touchActionMoveY - touchActionDownY)
float ratioUpDown = Math.abs(touchActionMoveY - touchActionDownY)/Math.abs(touchActionMoveX - touchActionDownX)
if(touchActionMoveX < touchActionDownX && ratioLeftRight > threshold){
Log.i("test","Move Left");
touchActionMoveStatus = false;
}
else if(touchActionMoveX > touchActionDownX && ratioLeftRight > threshold){
Log.i("test","Move Right");
touchActionMoveStatus = false;
}
else if(touchActionMoveY < touchActionDownY && ratioUpDown > threshold){
Log.i("test","Move Up");
touchActionMoveStatus = false;
}
else if(touchActionMoveY > touchActionDownY && ratioUpDown > threshold){
Log.i("test","Move Down");
touchActionMoveStatus = false;
}
}
break;
}
// return false;
return true; // This gets the coordinates all the time
}
I would choose the dimension with the LARGEST movement and completely ignore the other, for example if the move is x=10 and y=8 then only use the x dimension (i.e. left/right) and vice versa.
Also as noted by Larry McKenzie, using a threshold to ignore smaller movements is a good idea to prevent registering accidental movements that the user did not intend. Tweak the threshold value to someting that feels natural.
Here is some code using your example (only the ACTION_MOVE case):
case MotionEvent.ACTION_MOVE:
//Log.i("test","Move");
gameLoop.touchActionMove = true;
if(touchActionMoveStatus) {
touchActionMoveX = (int)event.getX();
touchActionMoveY = (int)event.getY();
// setup a threshold (below which no movement would occur)
int threshold = 5; /* tweak this as needed */
// first calculate the "delta" movement amounts
int xMove = touchActionMoveX - touchActionDownX;
int yMove = touchActionMoveY - touchActionDownY;
// now find the largest of the two (note that if they
// are equal, x is assumed largest)
if ( Math.abs( xMove ) >= Math.abs( yMove ) ) { /* X-Axis */
if ( xMove >= threshold )
Log.i("test","Move Right");
else if ( xMove <= -threshold )
Log.i("test","Move Left");
}
else { /* Y-Axis */
if ( yMove >= threshold )
Log.i("test","Move Down");
else if ( yMove <= -threshold )
Log.i("test","Move Up");
}
touchActionMoveStatus = false; // Will be set to true when pointer is up
}
}
break;
NOTE: As mentioned in some of the other answers, because multiple events with very small values can occur, it might be best to accumulate (i.e. sum up) the movements UNTIL the threshold is reached - you can use members for this that reset in ACTION_DOWN. Once the threshold is reached (in either dimension) THEN you can perform the checks for which direction.
Alternative Approach
Another way to go about it would be to detect the largest movement in the FIRST ACTION_MOVE event, and then lock all further movements to that dimension. For this you would need to add various state members - these would need to be updated in each state.
Here is a rough example (with only the state tracking):
// members
private boolean axisLock = false; /* Track When Lock is Required */
private boolean axisX = true; /* Axis to Lock (true) for X, (false) for Y */
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// set this state so that ACTION_MOVE knows a lock is required
axisLock = true;
break;
case MotionEvent.ACTION_UP:
// clear the state in case no move was made
axisLock = false;
case MotionEvent.ACTION_MOVE:
// now lock the axis if this is the first move event
if ( axisLock ) {
// this will set whether the locked axis is X (true) or Y (false)
axisX = event.getX() >= event.getY();
// reset the state (to keep the axis locked)
axisLock = false;
}
// at this point you only need to consider the movement for the locked axis
if ( axisX ) {
int movement = (int)event.getX(); /* Get Movement for Locked Axis */
// check for your movement conditions here
}
else {
int movement = (int)event.getY(); /* Get Movement for Locked Axis */
// check for your movement conditions here
}
break;
}
return true;
}
You could add many optimizations to this code, for now it just illustrates the basic idea.
larry had the right idea, i just want to put in a lil fix,
//put this in the wraping class
private static int THRESHOLD = 10;
private static int initX;
private static int initY;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initX = (int)event.getX();
initY = (int)event.getY();
break;
case MotionEvent.ACTION_POINTER_UP:
//you can add in some kind of "move back" animation for the item
break;
case MotionEvent.ACTION_MOVE:
if(((int)event.getY - initY) > THRESHOLD){
//move down
break;
}
if(((int)event.getY - initY) > -THRESHOLD){
//move up
break;
}
if(((int)event.getX - initX) > THRESHOLD){
//move right
break;
}
if(((int)event.getX - initX) < -THRESHOLD){
//move left
break;
}
break;
}
}
i didn't test this code, only free write it, but i hope you get my idea :)

Motionevent Action_MOVE keeps firing X and Y even if not moved

I am trying out simple program that have sounds based if moved. so at the beginning I have down - which play sound 1 and from then every move it is keeps playing a sound. At count 4 I have made it to play from start.
here is the problem: When I don't move my finger and hold it in the same place the sound still keeps 1 by 1 - Figured out the x and y value firing. How do I stop this??
OnTouchListener MyOnTouchListener= new OnTouchListener()
{
public boolean onTouch(View view, MotionEvent event)
{
switch(event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
x = (int) event.getX();
y = (int) event.getY();
oldval = x+y;
break;
case MotionEvent.ACTION_MOVE:
{
Log.e("X value", "X is "+x);
Log.e("Y value", "Y is "+y);
try
{
Thread.sleep(500);
} catch (InterruptedException e) {
}
int newval= (int) (event.getX() + event.getY());
if(Math.abs(oldval-newval)>50)
{
Log.e("First", "next button");
longpressCount++;
if(longpressCount==1)
{
Log.e("1", "BUTTON PRESSED");
}
else if(longpressCount==2)
{
Log.e("2", "BUTTON PRESSED");
}
else if(longpressCount==3)
{
Log.e("3", "BUTTON PRESSED");
}
else if(longpressCount==4)
{
Log.e("4", "BUTTON PRESSED");
longpressCount = 0;
}
}
break;
}
}
return true;
}
MOVE is very sensitive and will continue to be called as long as your finger is down. Set Old Value at the end of the sound playing code so it will only play if moved another 50 distance from that spot.
Something like this.
OnTouchListener MyOnTouchListener= new OnTouchListener()
{
public boolean onTouch(View view, MotionEvent event)
{
switch(event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
x = (int) event.getX();
y = (int) event.getY();
oldval = x+y;
break;
case MotionEvent.ACTION_MOVE:
{
Log.e("X value", "X is "+x);
Log.e("Y value", "Y is "+y);
try
{
Thread.sleep(500);
} catch (InterruptedException e) {
}
int newval= (int) (event.getX() + event.getY());
if(Math.abs(oldval-newval)>50)
{
Log.e("First", "next button");
longpressCount++;
if(longpressCount==1)
{
Log.e("1", "BUTTON PRESSED");
}
else if(longpressCount==2)
{
Log.e("2", "BUTTON PRESSED");
}
else if(longpressCount==3)
{
Log.e("3", "BUTTON PRESSED");
}
else if(longpressCount==4)
{
Log.e("4", "BUTTON PRESSED");
longpressCount = 0;
}
oldval = event.getX() + event.getY();
}
break;
}
}
return true;
}

MotionEvent.Action_up called to early

I am having an issue with MotionEvent.ACTION_UP The event is called before I lift my finger.
Here is the code I am using. What should I change? Thanks for any help!
public boolean onTouchEvent(MotionEvent e) {
switch(e.getAction()) {
case MotionEvent.ACTION_DOWN:
if(checkColide(e.getX(), e.getY())) {
isFootballTouched = true;
downT = c.MILLISECOND;
downX = e.getX();
}
break;
case MotionEvent.ACTION_MOVE:
//moveFootball(e.getX(), e.getY());
break;
case MotionEvent.ACTION_UP:
upT = c.MILLISECOND;
upX = e.getX();
getVelocity();
break;
}
return false;
}
Try returning true if one of this 3 case occurred
public boolean onTouchEvent(MotionEvent e) {
switch(e.getAction()) {
case MotionEvent.ACTION_DOWN:
if(checkColide(e.getX(), e.getY())) {
isFootballTouched = true;
downT = c.MILLISECOND;
downX = e.getX();
}
return true;
case MotionEvent.ACTION_MOVE:
//moveFootball(e.getX(), e.getY());
return true;
case MotionEvent.ACTION_UP:
upT = c.MILLISECOND;
upX = e.getX();
getVelocity();
return true;
}
return false;
}
Probably you should return true from the onTouchEvent(). Returning false means that you're not interested in receiving events to this View anymore. Hope this helps.

Categories

Resources