I'm trying to catch the ACTION_UP and ACTION_CANCEL on my RecyclerView, but I want every other event to be caught by it's children, but it seems the child that caught the first event is the only one that catches all the following ones. Here my code for the intercept event.
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
boolean takeEventControl = false;
switch (e.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
takeEventControl = true;
break;
case MotionEvent.ACTION_MOVE:
// Some processing
takeEventControl = false;
break;
}
return takeEventControl;
}
#Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
// Some code
}
And here is my code for the child views
#Override
public boolean onTouch(final View view, final MotionEvent event) {
switch (e.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
//Some processing
break;
case MotionEvent.ACTION_MOVE:
//Some processing
break;
}
return true;
}
I already tested it and find that if I return true the first time the onInterceptTouchEvent is triggered the onTouchEvent is called successfully, but in my code I need to call the child onTouch when the event action is ACTION_DOWN or ACTION_MOVE, and the onTouchEvent on ACTION_UP or ACTION_CANCEL but the last one is never called, I hope someone could explain me what's happening here.
To avoid having a view control touches after the ACTION_DOWN, have all views return false in their onTouch for ACTION_DOWN. You can then return true for other touch events that are truly being handled.
The reason behind this is the first view to return true for ACTION_DOWN will be the only view to receive future onTouch calls until after an ACTION_UP or ACTION_CANCEL is received.
Related
I have a custom view which acts as a button. I am drawing all the canvas myself. Now I'm making an outline when ACTION_DOWN and remove it after ACTION_UP or ACTION_CANCEL.
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e("test", "ACTION_DOWN");
break;
case MotionEvent.ACTION_UP:
Log.e("test", "ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e("test", "ACTION_CANCEL");
break;
}
return true;
}
This can work for me, except now it is blocking another gesture behind this view which is detecting ACTION_MOVE (scroll left).
If I return false, then it is working fine but now ACTION_UP is not called.
I want to call ACTION_UP if finger is lifted, but pass events down otherwise.
Have you tried overriding dispatchTouchEvent?
https://developer.android.com/reference/android/view/View.html#dispatchTouchEvent(android.view.MotionEvent)
UPDATE:
So touch events are a bit of a beast. The rundown of it is this...
They bubble up at first from your root container in your Activity. This is done by calling dispatchTouchEvent and then onInterceptTouchEvent assuming intercepting wasn't blocked by a child view.
If no view intercepts the event, it will bubble to the leaf node (such as a button) where onTouch is called. If the node doesn't handle it (returns true) its parent gets a chance and so on.
This means that you can use dispatchTouchEvent or onInterceptTouchEvent to spy on touch events without changing the behavior. Unless you're actually going to intercept the event I suggest using dispatchTouchEvent as it's guaranteed to run whereas intercepting may be blocked (example: DrawerLayout will intercept touch events near the edge in order to open the drawer).
So the final result is:
public class MyView extends Button {
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
Log.e("test", "ACTION_DOWN");
break;
case MotionEvent.ACTION_UP:
Log.e("test", "ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e("test", "ACTION_CANCEL");
break;
}
return super.dispatchTouchEvent(event);
}
}
UPDATE:
Sorry, so I've been under the impression for some reason (mostly my poor reading) that we were dealing with the parent. Here's what I would do...
Go ahead and implement onTouch and return true to consume all the events. This means that any touch events that start on your view will be eaten up. What we'll do then is translate the point to the parent's coordinate space and manually pass the touch event up, it'll look like this inside your custom view...
private boolean passingTouchEventToParent = true;
final private Rect hitRect = Rect();
#Override
public boolean onTouch(MotionEvent event) {
// Handle your custom logic here
final ViewParent viewParent = getParent();
if (passingTouchEventToParent &&
viewParent != null &&
viewParent instanceof View) {
// Gets this view's hit rectangle in the parent's space
getHitRect(hitRect);
event.offsetLocation((float) hitRect.left, (float) hitRect.top);
passingTouchEventToParent = viewParent.onTouchEvent(event);
}
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
// Motion event finished, reset passingTouchEventToParent
passingTouchEventToParent = true;
}
return true;
}
I'm trying to have a scrolling MapView inside of a RecyclerView, therefore I'm setting requestDisallowInterceptTouchEvent() before and after the TouchEvent.
The odd thing is: this does work if I set it in the dispatchTouchEvent() method, but it doesn't work if I do the same in the onTouchEvent() method.
Can somebody explain why I cannot set this in onTouchEvent()?
Working:
public class WorkingScrollableListItemMapView extends MapView {
// constructors
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// Stop parents from handling the touch
this.getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
// Allow parents from handling the touch
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(ev);
}
}
Not working:
public class NotWorkingScrollableListItemMapView extends MapView {
// constructors
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
// Allow parents from handling the touch
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
case MotionEvent.ACTION_DOWN:
// Stop parents from handling the touch
this.getParent().requestDisallowInterceptTouchEvent(true);
break;
}
return super.onTouchEvent(ev);
}
}
Call sequence for handling an event are somewhat in this order:
onInterceptTouchEvent, onDispatchTouchEvent, dispatchTouchEvent, onTouchEvent.
That, to me, indicates that the onTouchEvent is the very last step in processing an event. It would be too late to manipulate where & whom handles the event at the very last step. What does the source code say if you look at the earlier methods for handling the event?
As per android MotionEvent documents: A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP). The motion contains the most recent point, as well as any intermediate points since the last down or move event.
ACTION_MOVE Android doc
so when i apply a setOnTouchListene on my view is perfectly works, It give me ACTION_DOWN, ACTION_UP, and ACTION_MOVE
but my problem is i just want to ignore a ACTION_DOWN events exactly before ACTION_MOVE. Because ACTION_MOVE event occur only after the ACTION_DOWN event as per its documents.
my coding:
button.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.e("Mouse: ", "Click");
break;
case MotionEvent.ACTION_MOVE:
Log.e("Mouse: ", "Move");
break;
}
return false;
}
});
So, there is any why to ignore ACTION_DOWN event. Because user only want to move not want to click, and ACTION_MOVE is also occur ACTION_DOWN before it execute it self.
Thanks...
According to your comment - you can play with counter. For example:
private static int counter = 0;
...
button.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.e("Mouse: ", "Click");
break;
case MotionEvent.ACTION_MOVE:
counter++; //check how long the button is pressed
if(counter>10){
Log.e("Mouse: ", "Move");
}
break;
case MotionEvent.ACTION_UP:
if(counter<10){
//this is just onClick, handle it(10 is example, try different numbers)
}else{
//it's a move
}
counter = 0;
break;
}
return false;
}
});
I have
RelativeLayout
A---BIG IMAGE
B---MEDIUM IMAGE
C---SMALL IMAGE
The picture is looking like this
I have used below java code
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (v.getId()) {
// LEFT
case R.id.tblLOne:
System.out.println("IMG_L_A");
playBeep(TABLA_L_BIG);
changeLeftDrum();
break;
case R.id.tblLTwo:
System.out.println("IMG_L_B");
playBeep(TABLA_L_MID);
changeLeftDrum();
break;
case R.id.tblLThree:
System.out.println("IMG_L_C");
playBeep(TABLA_L_SMALL);
changeLeftDrum();
break;
return false;
}
return true;
}
Problem is that whenever I click on small (BLACK) Image
I got following output
IMG_L_A
IMG_L_B
IMG_L_C
Whenever I click on Middle Image I got
IMG_L_A
IMG_L_B
On OuterImage big image
IMG_L_A
Why I am getting it's all behind ImageView's OnTouch Method
It is working perfect with onClick but not with OnTouch
It's because the views are stacked on top of each other.
The important point here is to know the importance of the Boolean flag that you return from your onTouchListener. The boolean flag tells android if the event was consumed or not.
Suppose, you touch tblRthree, the case R.id.tblLThree executes, but then since you return false, it appears to android that the event was not consumed and this event bubbles up to the tblRTwo view which is just behind tblRthree view, which executes the same listener for the case R.id.tblLTwo but then again you return false so, it bubbles up to view tblROne and all three cases execute.
You should return true whenever you consume the event, and false when you don't.
onTouch method will be called in multiple events, all you need is to check whether it is MotionEvent.ACTION_DOWN or not.
So, it will look something like this:
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()!=MotionEvent.ACTION_DOWN)
{
return false;//we are not going to handle it
}
switch (v.getId()) {
// LEFT
case R.id.tblLOne:
System.out.println("IMG_L_A");
playBeep(TABLA_L_BIG);
changeLeftDrum();
break;
case R.id.tblLTwo:
System.out.println("IMG_L_B");
playBeep(TABLA_L_MID);
changeLeftDrum();
break;
case R.id.tblLThree:
System.out.println("IMG_L_C");
playBeep(TABLA_L_SMALL);
changeLeftDrum();
break;
}
return true;//we have handled it
}
I've got a custom view which acts like a button. I want to change the background when user press it, revert the background to original when user moves the finger outside or release it and I also want to handle onClick/onLongClick events. The problem is that onTouch requires me to return true for ACTION_DOWN or it won't send me the ACTION_UP event. But if I return true the onClick listener won't work.
I thought I solved it by returning false in onTouch and registering onClick - it somehow worked, but was kinda against the docs. I've just received a message from an user telling me that he's not able to long-click on the button, so I'm wondering what's wrong here.
Part of the current code:
public boolean onTouch(View v, MotionEvent evt)
{
switch (evt.getAction())
{
case MotionEvent.ACTION_DOWN:
{
setSelection(true); // it just change the background
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_OUTSIDE:
{
setSelection(false); // it just change the background
break;
}
}
return false;
}
public void onClick(View v)
{
// some other code here
}
public boolean onLongClick(View view)
{
// just showing a Toast here
return false;
}
// somewhere else in code
setOnTouchListener(this);
setOnClickListener(this);
setOnLongClickListener(this);
How do I make them work together correctly?
Thanks in advance
onClick & onLongClick is actually dispatched from View.onTouchEvent.
if you override View.onTouchEvent or set some specific View.OnTouchListener via setOnTouchListener,
you must care for that.
so your code should be something like:
public boolean onTouch(View v, MotionEvent evt)
{
// to dispatch click / long click event,
// you must pass the event to it's default callback View.onTouchEvent
boolean defaultResult = v.onTouchEvent(evt);
switch (evt.getAction())
{
case MotionEvent.ACTION_DOWN:
{
setSelection(true); // just changing the background
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_OUTSIDE:
{
setSelection(false); // just changing the background
break;
}
default:
return defaultResult;
}
// if you reach here, you have consumed the event
return true;
}