MotionEvent.ACTION_UP not called

AndroidListview

Android Problem Overview


Consider the following scheme below (for sake of better understanding of my problem). enter image description here

As you can see, I am considering a list view surrounded by padding. Now, If a user presses a listview item, as the action I have provided it light blue background color. Now, My application is dealing with onTouch Events itself to determine actions like

  • Click
  • Left to Right Swipe
  • Right to Left Swipe

Here is my code.

public boolean onTouch(View v, MotionEvent event) {
    	if(v == null)
    	{
    		mSwipeDetected = Action.None;
    		return false;
    	}
        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN: {
            downX = event.getRawX();
            downY = event.getRawY();
            mSwipeDetected = Action.Start;
            
         // Find the child view that was touched (perform a hit test)
			Rect rect = new Rect();
			int childCount = listView.getChildCount();
			int[] listViewCoords = new int[2];
			listView.getLocationOnScreen(listViewCoords);
			int x = (int) event.getRawX() - listViewCoords[0];
			int y = (int) event.getRawY() - listViewCoords[1];
			View child;
			for (int i = 0; i < childCount; i++) {
				child = listView.getChildAt(i);
				child.getHitRect(rect);
				if (rect.contains(x, y)) {
					mDownView = child;
					break;
				}
			}
			
			
            return false; // allow other events like Click to be processed
        }
        case MotionEvent.ACTION_MOVE: {
            upX = event.getRawX();
            upY = event.getRawY();
            float deltaX=0,deltaY=0;
             deltaX = downX - upX;
             deltaY = downY - upY;

             	if(deltaY < VERTICAL_MIN_DISTANCE)
             	{
                        	setTranslationX(mDownView, -(deltaX));
                        	setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / listView.getWidth())));
                        	return false;
             	}
             	else
             	{
             		forceBringBack(v);
             	}
                 
                          return false;              
                    
        }
        case MotionEvent.ACTION_UP:
        {

             stopX = event.getX();
             float stopValueY = event.getRawY() - downY;             
             float stopValue = stopX - downX;
             
             if(!mDownView.isPressed())
             {
            	 forceBringBack(mDownView);
            	 return false;
             }             
                          
             boolean dismiss = false;
             boolean dismissRight = false;
             
             
             if(Math.abs(stopValue)<10)
             {
            	 mSwipeDetected = Action.Start;
             }
             else
             {
            	 mSwipeDetected = Action.None;
            	 
             }
             String log = "";
             Log.d(log, "Here is Y" + Math.abs(stopValueY));
             Log.d(log, "First Comparison of Stop Value > with/4" + (Math.abs(stopValue) > (listView.getWidth() /4)));
             Log.d(log, "Second Comparison " + (Math.abs(stopValueY)<VERTICAL_MIN_DISTANCE));
             Log.d(log, "Action Detected is " + mSwipeDetected + " with Stop Value  " + stopValue);
             
             if((Math.abs(stopValue) > (listView.getWidth() /4))&&(Math.abs(stopValueY)<VERTICAL_MIN_DISTANCE))
             {
            	 dismiss = true;
            	 dismissRight = stopValue > 0;
            	 
            	 if(stopValue>0)
            	 {
            	 mSwipeDetected = Action.LR;
            	 
            	 }
            	 else
            		 mSwipeDetected = Action.RL;
             }
             Log.d(log, "Action Detected is " + mSwipeDetected + " with Stop Value after dissmiss" + stopValue);
             
             if(dismiss)
             {
            	 if(dismissRight)
            		 mSwipeDetected = Action.LR;
            	 else
            		 mSwipeDetected = Action.RL;
            	 animate(mDownView)
            	 .translationX(dismissRight ? listView.getWidth() : - listView.getWidth())
            	 .alpha(0)
            	 .setDuration(mAnimationTime)
            	 .setListener(new AnimatorListenerAdapter() {
            		 public void onAnimationEnd(Animator animation)
            		 {
            			 
            		 }
				});
             }
             else
             {
            	 animate(mDownView)
            	 .translationX(0)
            	 .alpha(1)
            	 .setDuration(mAnimationTime)
            	 .setListener(null);
             }
             
             
             break;           
             
        }
        }
        return false;
    }

As you can see, I determine the performed action in MotionEvent.ACTION_UP and set the value of Enum Action accordingly. This logic works like a charm if the user does not crosses the list view boundary.

Now, if the user, while sliding (or specifically), moving his finger along the list item moves from blue to orange, the MotionEvent.ACTION_UP would not be given to listview, which causes my code not to make a decision and due to translationX() method and setAlpha(), since no Action is ever determined in this case, that particular list item gets blank.

The problem does not stops here, since, I am not inflating view each time, same translatedX() row gets inflated each time leading to multiple occurance of a blank/white list item.

Is there anything possible to do so that even if I didn't encounter MotionEvent.ACTION_UP, I could still make some decison ?

Android Solutions


Solution 1 - Android

You should return true; in case MotionEvent.ACTION_DOWN:, so the MotionEvent.ACTION_UP will be handled.


As explained on View.OnTouchListener:

> Returns: > > True if the listener has consumed the event, false otherwise.

MotionEvent.ACTION_UP Won't get called until the MotionEvent.ACTION_DOWN occurred, a logical explanation for this is that it's impossible for an ACTION_UP to occur if an ACTION_DOWN never occurred before it.

This logic enables the developer to block further events after ACTION_DOWN.

Solution 2 - Android

Also note that under certain circumstances (eg. screen rotations) the gesture may be cancelled, in which case a MotionEvent.ACTION_UP will NOT be sent. Instead a MotionEvent.ACTION_CANCEL is sent instead. Therefore a normal action switch statement should look something like this:

switch (event.getActionMasked()) {
	case MotionEvent.ACTION_DOWN:
		// check if we want to handle touch events, return true
		// else don't handle further touch events, return false
	break;

	// ... handle other cases
		 
	case MotionEvent.ACTION_UP:
	case MotionEvent.ACTION_CANCEL:
		// finish handling touch events
		// note that these methods won't be called if 'false' was returned
		// from any previous events related to the gesture
	break;
}

Solution 3 - Android

I don't think adding return true;to case MotionEvent.ACTION_DOWN: would eventually solve the problem. It just complicated the situation where return false could have done the job like a charm.

What to notice is: MotionEvent.ACTION_DOWN: /*something*/ return true; will block any other Listener callbacks available for the view, even onClickListenerm, while correctly return false in MotionEvent.ACTION_UP: can help the MotionEvent be propagated to the right destination.

Reference to his original code sourse: https://github.com/romannurik/android-swipetodismiss

Solution 4 - Android

As Danpe explained in his concise answer - I had to add the ACTION_DOWN code in order to have ACTION_UP recognized.

			case MotionEvent.ACTION_DOWN:

				return true;
				
			case MotionEvent.ACTION_UP:
				
				XyPos xyPos = new XyPos();
				xyPos.x = last_x;
				xyPos.y = last_y;
				handleViewElementPositionUpdate(xyPos);
				
				break;
				

I had the entire onTouch(..) method returning true anyway, so I'm not sure why that wasn't enough ... but nice to have this quick solution .. (thanks!)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGauravView Question on Stackoverflow
Solution 1 - AndroidDanpeView Answer on Stackoverflow
Solution 2 - AndroidTheITView Answer on Stackoverflow
Solution 3 - AndroidChrysanthemumerView Answer on Stackoverflow
Solution 4 - AndroidGene BoView Answer on Stackoverflow