I am having this problem where ACTION_CANCEL is not triggered, I have implemented it in my other project and it's working fine. It seems that ACTION_UP is the only MotionEvent that is called after ACTION_DOWN. I wanted to trigger ACTION_CANCEL once my finger is not anymore in the view or outside the screen.
Sample scenario: I click on the view which is a LinearLayout btw, on ACTION_DOWN its background is changed to a "clicked/dimmed" version of the image and when ACTION_UP is triggered its background changes back to the default image only if the finger is within the LinearLayout. Now the problem is when I press it and kept my finger on the screen, and drag my finger outside the LinearLayout, ACTION_UP is still triggered where it shouldn't have.
Here's my code:
dimView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(final View view,
final MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("TAG", "DOWN");
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
Log.d("TAG", "UP");
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
Log.d("TAG", "CANCEL");
return true;
}
return false;
}
});
where: dimView is a LinearLayout
I've been debugging this for a really long time and it bothered me a lot since this came up very randomly. I then tested my code on different devices and realized the API implementation changed with Android 4.2.
The expected behaviour and code work absolutely fine with Android 4.1.2 (tested on Galaxy Tab 2) but the bug you describe can be seen on a Nexus 7 (Android 4.2).
Apparently Android changed the way MotionEvents are handled in API 17.
One particular case when the bug does not occur is when the view is located in a GroupLayout under a ScrollView. When scrolling is possible the ACTION_CANCEL gets fired. However when no scrolling is possible the bug persists.
At first I tried combining an OnClickListener and OnTouchListener so that the last can handle just the animations but to no avail. Dispatching the Events from parents also doesn't work.
One workaround is to capture ACTION_MOVE events and check if the finger is located outside of the view's boundaries using v.getX() and v.getY() and comparing them to event.getX() and event.getY() respectably. A global boolean variable (isOutside) can be used to store the most current information. Before firing up the ACTION_UP you can check the latest state of isOutside and perform your animations and action accordingly. You can also return true or false depending on whether you captured the event or not.
Update: After digging a bit here i found this solution:
Android: Detect if user touches and drags out of button region? and compiled this code. The idea is the same except that it creates a rectangle and checks if the event boundaries are within the rectangle of the view.
someView.setOnTouchListener(new View.OnTouchListener() {
private Rect rect;
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG,"Touched: "+event.getAction());
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.d(TAG,"ACTION_DOWN");
animateImageButtonOnClick(v, event);
rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
}
if (event.getAction() == MotionEvent.ACTION_UP) {
Log.d(TAG,"ACTION_UP");
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
Log.d(TAG,"ACTION_UP - outside");
animateImageButtonOnRelease(v, event);
} else {
Log.d(TAG,"ACTION_UP - inside");
// do your stuff here
}
}
if(event.getAction() == MotionEvent.ACTION_MOVE){
if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
animateImageButtonOnReleaseWithLowDuration(v, event);
}
}
if (event.getAction() == MotionEvent.ACTION_CANCEL){
Log.d(TAG,"ACTION_CANCEL");
animateImageButtonOnRelease(v, event);
return true;
}
return true;
}
});
Override dispatchTouchEvent method maybe deal with it;
dimView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
// to call super touch method
return false;
}
});
#Override
public boolean dispatchTouchEvent(MotionEvent motionEvent) {
// TODO Auto-generated method stub
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("TAG", "DOWN");
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
Log.d("TAG", "UP");
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
Log.d("TAG", "CANCEL");
return true;
}
return super.dispatchTouchEvent(motionEvent);
}
Related
OnTouchEvent is being fired after the second time touch; yet, I want it to be fired after the first touch itself.
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent event) {
//too avoid touch being detected continuously
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (Function.system[5] == "Gravity: on") {
//some action...
}
}
return super.onTouchEvent(event)
}
You need to tell Android to continue to respond to touch events. Returning true let's you capture events from action down , action move and then action up.
Please add the return true for each event
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent event) {
//too avoid touch being detected continuously
if (event.getAction() == MotionEvent.ACTION_DOWN) {
}
return true;
}
Please try and seee if this works for you
I am following the google developer documentation:
https://developer.android.com/training/gestures/viewgroup.html
I am doing the exact same case as their example code, where I have a swipe gesture on a parent, but if I am not swiping, then I will handle the click on the child as opposed to intercepting on the parent.
However the current flow is like this:
Action.DOWN - > Hits Parent's onInterceptTouchEvent I return false since I am not waiting for this case
Action.DOWN - > Hits Child's onTouch Handler, I return false since I still want to possibly use the parents code
Action.DOWN -> Hits Parents' onTouch Handler, I return true because i want to get the rest of the notifications for the gesture and then pass on to the child when appropriate. If I return false at this point, then the event will die and I wont get Action.UP
At this point in the gesture I am strictly relegated to the Parent's onTouchListener and feel pigeonholed there since there are conditions where I would like to go back down to the child's onTouch handler. Particularly confounding are the following stipulations in the Android documentation:
The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal.
For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().
So my desire is to get at least up until the point of Action.MOVE within the parent's onInterceptTouchEvent so that I can determine whether or not this gesture is the childs click/touch or the parent's swipe. I can only seem to get to ACTION_UP in my parent's onTouchEvent since I return true at Action.DOWN which relegates my entirely, as the documentation says, to my parents onTouch method.
How can i keep the onInterceptTouchEvent spinning while also implmenting the onTouchEvent never return true until the moment I want the parent to do something in lieu of the child?
That is my logical idea, but when I do that, I have to make my parent's onTouch return false for the ActionDown which then kills the event from both onTouch and onInterceptTouchEvent paradigms.
Parent's on touch listner:
binding.linLayoutWrapper.setOnTouchListener(new View.OnTouchListener() {
int downX, moveX, upX;
int downY, moveY;
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
//binding.linLayoutWrapper.getParent().requestDisallowInterceptTouchEvent(true);
downX = (int) motionEvent.getX();
downY = (int) motionEvent.getY();
return false;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
return false;
} else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {
moveY = (int) motionEvent.getY();
moveX = (int) motionEvent.getX();
if (Math.abs(downX - moveX) > Math.abs(downY - moveY)){
upX = (int) motionEvent.getX();
if (upX - downX > 100) {
// swipe right
CalendarDay cal = binding.calendarView.getCurrentDate();
Calendar cal1 = Calendar.getInstance();
cal1.setTime(cal.getDate());
cal1.add(Calendar.WEEK_OF_YEAR, -1);
binding.calendarView.setCurrentDate(cal1.getTime());
binding.linLayoutWrapper.getParent().requestDisallowInterceptTouchEvent(true);
return true;
} else if (downX - upX > 100) {
CalendarDay cal = binding.calendarView.getCurrentDate();
Calendar cal1 = Calendar.getInstance();
cal1.setTime(cal.getDate());
cal1.add(Calendar.WEEK_OF_YEAR, 1);
binding.calendarView.setCurrentDate(cal1.getTime());
binding.linLayoutWrapper.getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
} else {
binding.linLayoutWrapper.getParent().requestDisallowInterceptTouchEvent(false);
}
}
return false;
}
});
Parents onInterceptTouchListener:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
Log.d("HELP", " in parent action up");
mIsScrolling = false;
return false;
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
if (mIsScrolling) {
return true;
}
final int xDiff = calculateDistanceX(ev);
if (xDiff > mTouchSlop) {
// Start scrolling!
mIsScrolling = true;
return true;
}
break;
}
case MotionEvent.ACTION_DOWN: {
xNot = ev.getX();
return false;
}
}
return false;
}
Finally my child's onTouch listener:
relativeLayout.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
Intent intent = new Intent(getContext(), EditAvailabilityActivity.class);
if (event != null) {
int nurseId = AccountManager.sharedManager().getCurrentAccount().getId();
Conflict conflict = new Conflict();
conflict.setNurseId(nurseId);
conflict.setId(event.getConflictId());
conflict.setEndDate(event.getParentEnd());
conflict.setStartDate(event.getParentStart());
conflict.setStartTime(event.getStartTime());
conflict.setEndTime(event.getEndTime());
conflict.setIsAllDay(event.getAllDay() == 1);
intent.putExtra(EditAvailabilityActivity.EXTRA_CONFLICT, Parcels.wrap(conflict));
}
intent.putExtra(EditAvailabilityActivity.EXTRA_MODE, true);
((Activity) getContext()).startActivityForResult(intent, EDIT_AVAILABILITY_REQUEST_CODE);
return true;
}
return false;
}
});
UPDATE:
If I return true from the child's onTouchListener then it goes to the parent's onInterceptTouchEvent. I am not sure how that jives with what android says when they say " Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal." It seems like a flagrant contradiction, but maybe they mean "when you return true from the PARENT's onTouchListener" IS that accurate?
When I change my childs onTouchListener to this:
relativeLayout.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
Intent intent = new Intent(getContext(), EditAvailabilityActivity.class);
if (event != null) {
int nurseId = AccountManager.sharedManager().getCurrentAccount().getId();
Conflict conflict = new Conflict();
conflict.setNurseId(nurseId);
conflict.setId(event.getConflictId());
conflict.setEndDate(event.getParentEnd());
conflict.setStartDate(event.getParentStart());
conflict.setStartTime(event.getStartTime());
conflict.setEndTime(event.getEndTime());
conflict.setIsAllDay(event.getAllDay() == 1);
intent.putExtra(EditAvailabilityActivity.EXTRA_CONFLICT, Parcels.wrap(conflict));
}
intent.putExtra(EditAvailabilityActivity.EXTRA_MODE, true);
((Activity) getContext()).startActivityForResult(intent, EDIT_AVAILABILITY_REQUEST_CODE);
return true;
}
return true; //this makes it behave strangely
}
});
It effectively handles the click but it seems to do so in a weird way. It violates the android documentation by going back to the parent's oninterceptTouchEvent all the way through Action_UP. This seemed promising because if i could get that far along the gesture and still get the right outcome from the target's onTouch method then I would get what i was looking for. Sadly in this case, when I try to do a swipe gesture I only get the following rapartee between parent and child:
Action.DOWN - > Parent's onInterceptTouchEvent
Action.Down -> Hits Child's OnTouchListener which is returning "true"
Action.Cancel -> Hits Parent's onInterceptTouchEvent
This is the expected flow, so it was wishful thinking that it work but it was not in line with the earlier contradiction I noticed.
I'm trying just trying to make this function work but I am having troubles. When I have touch on View, getPointerCount() method always returns 0 pointers, when I touch the screen with one finger, 1. When I touch the screen with two or more fingers, it's always return 1. Have you any ideas ?
i have trying to this code,
#Override
public boolean onTouch(View view, MotionEvent me) {
// No dragging during animation at the moment.
// TODO: Stop animation on touch event and return to drag mode.
if (me.getPointerCount() >= 2) {
mAnimate = false;
mEnableTouchPressure = false;
mRenderLeftPage = false;
startCurl(CURL_NONE);
mCurlState = CURL_NONE;
mPageRight.setFlipTexture(false);
mPageLeft.setFlipTexture(false);
return false;
} else {
if (me.getAction() == MotionEvent.ACTION_DOWN) {
Log.e("TAG_EVENT", me.getPointerCount() + "");
} else {
Log.e("TAG_EVENT - 2", me.getPointerCount() + "");
}
}
}
I think is because you should return true for the touch event to be processed.
You can read the about MotionEvent.ACTION_MASK. For catching the action of multiTouch you can use the following code :
int action = (motionEvent.getAction() & MotionEvent.ACTION_MASK) % 5
switch(action) {
case MotionEvent.ACTION_DOWN:
System.out.println(motionEvent.getPointerCount());
break;
}
Hi i want to pass the touch event to the parent, only if the touch has moved, when the user clicks i want to handle it within the child, so i tried this within the child:
private boolean moved = false;
#Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN) {
super.onTouchEvent(event);
moved = false;
return false;
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
moved = true;
return false;
}
if (event.getAction() == MotionEvent.ACTION_UP) {
return !moved;
}
return true;
}
but when i return false on ACTION_DOWN i do not get ACTION_UP
On the time ACTION_DOWN occours i do not know if i will handle it or not
According to this official Android developer site link (Read Capturing touch events for a single view section)
Beware of creating a listener that returns false for the
ACTION_DOWN event. If you do this, the listener will not be called
for the subsequent ACTION_MOVE and ACTION_UP string of events.
This is because ACTION_DOWN is the starting point for all touch
events.
"If you return true from an ACTION_DOWN event you are interested in the rest of the events in that gesture. A "gesture" in this case means all events until the final ACTION_UP or ACTION_CANCEL. Returning false from an ACTION_DOWN means you do not want the event and other views will have the opportunity to handle it. If you have overlapping views this can be a sibling view. If not it will bubble up to the parent."
- What is meaning of boolean value returned from an event-handling method in Android
When you return false you don't consume touch event and it is passed to the parent (and as you've found out by experiment you can't proceed on that gesture), if you return true - you take responsibility of processing.
So try this
private boolean moved = false;
#Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// CALL YOUR PARENT onTouch() IF YOU NEED TO
moved = false;
}
else if (event.getAction() == MotionEvent.ACTION_MOVE)
moved = true;
else if (event.getAction() == MotionEvent.ACTION_UP)
return !moved;
return true;
}
I am writing a simple code on ontouchevent..the code is
class MyTouchListener implements View.OnTouchListener{
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==MotionEvent.ACTION_DOWN)
{ Log.i(TAG, "^^^^^^^^^^^ACTION DOWN^^^^^^^^^^^^");
}
else if(event.getAction()==MotionEvent.ACTION_UP)
{
Log.i(TAG, "^^^^^^^^^^^ACTION UP^^^^^^^^^^^^");
}
}
while i pressed the screen it prints ^^^^^ACTION DOWN^^^^^
But when I released the screen it does not print ^^^^^^ACTION UP^^^^^^....
means MotionEvent.ACTION_UP fails..why this is so??
Try with MotionEvent.ACTION_CANCEL
#Override
public boolean onTouch(final View view, final MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
setColorFilter(Color.argb(155, 185, 185, 185));
}
else if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
setColorFilter(Color.argb(0, 185, 185, 185));
}
return false;
}
or add a log in your ontouch method
Log.d("tag", "motionEvent=" + motionEvent);
well ACTION_DOWN is happening just once after that action move should be happening and in the and action up...
but there is one problem , for example if you have a touchable area of 48dip x 48dip and if you touch that area and move you finger away (while dragging) I mean you press and you drag you finger away from the touchable area than action up will not happen !!!
Action up will happen only in case when you take your finer of the screen but you are still in the touchable area. I hope you understand me what I am trying to say.
if you want mouse up to happen that touch the touchable area do not drag you finger away and take you finger away from the screen, you know just like taping then the up event will happen