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;
}
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 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);
}
How can I know the view is in touch state.
If more than one touch points on one view,How can I catch the event of the last up touch point.
Please help?
You can override onTouchEvent() on your View. ACTION_DOWN will be given when the first "pointer" is placed. From then on, you will get ACTION_POINTER_DOWN or ACTION_POINTER_UP as subsequent fingers are pressed down and then released. Then, when the last pointer/finger is released, your View will get ACTION_UP. This is spelled out clearly in the MotionEvent docs.
Something like this might be what you're looking for, just subclass whatever View you are working with.
#Override
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN)
isTouching = true;
else if(event.getAction() == MotionEvent.ACTION_UP)
isTouching = false;
return super.onTouchEvent(event);
}
I have a custom view which I call "Node" that is a child of a custom ViewGroup called "NodeGrid". The "NodeGrid" class more specifically extends RelativeLayout.
I have the following code snippet in my custom view class ("Node"):
private boolean isBeingDragged = false;
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
isBeingDragged = true;
}
else if (event.getAction() == MotionEvent.ACTION_UP)
{
isBeingDragged = false;
}
else if (event.getAction() == MotionEvent.ACTION_MOVE)
{
if (isBeingDragged)
{
float xPosition = event.getX();
float yPosition = event.getY();
//change the x and y position here
}
}
return false;
}
The problem:
After having set breakpoints in this code, it seems like onTouchEvent is getting called only for the MotionEvent.ACTION_DOWN case, but not for either of the other two cases ("action up" or "action move"). Does anyone know of anything off hand that could be causing this to happen?
Also (could be related):
Does it matter how the view is added to the ViewGroup? I noticed that in addition to "addView" there are other methods for adding children to a ViewGroup such as "addFocusables" and "addTouchables". Right now I am simply adding the child view to the ViewGroup using "addView".
From the SDK Documentation:
onTouch() - This returns a boolean to indicate whether your listener consumes this event. The important thing is that this event can have multiple actions that follow each other. So, if you return false when the down action event is received, you indicate that you have not consumed the event and are also not interested in subsequent actions from this event. Thus, you will not be called for any other actions within the event, such as a finger gesture, or the eventual up action event.
You need to return true when the ACTION_DOWN event is triggered to indicate that you are interested in the subsequent calls relating to that same event.
HTH