I'm trying to use a custom view that I've written inside a CollapsingToolbarLayout, but it seems that the touch events are not propagating properly to my custom view with gesture detection. The result is that scrolling and interactions with the view are not working as expected or smoothly. My custom view makes heavy use of GestureDetector.SimpleOnGestureListener
Is it possible to embed a custom view which has it's own touch events inside CollapsingToolbarLayout?
After some investigation, I found that the problem was with onTouchEvent, specifically, you need to requestDisallowInterceptTouchEvent for the parent view so that the paraent view does not process the touch event:
public boolean onTouchEvent(MotionEvent event) {
// Do stuff on touch
// prevent parent container from processing ACTION_MOVE events
if(event.getAction() == MotionEvent.ACTION_MOVE) {
getParent().requestDisallowInterceptTouchEvent(true);
} else if(event.getAction() == MotionEvent.ACTION_CANCEL) {
getParent().requestDisallowInterceptTouchEvent(false);
}
// Do some more stuff
return true;
}
Related
I have a ScrollView with a LinearLayout and then a bunch of TextViews inside that layout, the goal is to make a CustomListView that will open and close various TextViews along the LinearLayout However, I also want to make an autoscroll feature where when the user double taps the screen the screen will scroll by its self.
I already have the second part done, when ever the user touches the ScrollView I have extended ScrollView and implemented this:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("ScrollView Intercept", "ACTION_DOWN");
return true;
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
Log.d("ScrollView Intercept", "ACTION_UP");
return true;
} else if (ev.getAction() == MotionEvent.ACTION_CANCEL) {
Log.d("ScrollView Intercept", "ACTION_CANCEL");
}
return super.onInterceptTouchEvent(ev);
}
Now the ScrollView has all the touch events.
However, I still want the events for the CustomListView, I have tried to implement a equestDisallowInterceptTouchEvent but it doesnt seem to be working:
#Override
public boolean onTouchEvent(MotionEvent event) {
getParent().getParent().requestDisallowInterceptTouchEvent(true);
Log.d("touch listner","Touch");
return true;
}
PS. I have tried to se ListView in the past, but it was a but confusing so I just extended the TextView class and wanted to use on click and manually make the children Visible and Gone
I have an ImageView overlay inside of a RelativeLayout and want to prevent any clicks from going through the ImageView to the Buttons etc that are behind it (thus in effect disabling the entire RelativeLayout).
Is the a simpler way of doing this then iterating the RelativeLayout views and setting them to disabled as I currently am doing using this code:
RelativeLayout rlTest = (RelativeLayout ) findViewById(R.id.rlTest);
for (int i = 0; i < rlTest.getChildCount(); i++) {
View view = rlTest.getChildAt(i);
view.setEnabled(true);
}
you can set the image to be
android:clickable="true"
Simply call rlTest.setClickable(false). This will prevent the click to be propagate to the children
There is a much cleaner way
You can use:
android:onClick="preventClicks"
in XML and in your Activity
public void preventClicks(View view) {}
This works with fragments.
Example inside this Activity has multiple fragments overlapping one another, just by adding the XML attribute in the background of your fragment it will still call the Activity.preventClicks and will prevent touches on fragments behind it
The following solution works in the general case:
_container.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// NOTE: This prevents the touches from propagating through the view and incorrectly invoking the button behind it
return true;
}
});
It basically blocks any touches from propagating through the view by marking the touch event as handled.
This works on both UI controls and layout containers (ie: LinearLayout, FrameLayout etc.).
The solution to set "clickable" as false did not work for me for layout containers either in code or in the view XML.
I assume that you are using onClickListeners.
How about using onTouchListener instead of onClickListeners. By doing this you will have a control over how deep down in your hierarchy the touch even can be visible. For example, if you have toch listeners on a relative-layout(RL) and a image-view(IV)(contained in RL), and you assign touchListeners to both. Now if you return true from IV's touch event, the lower down member RL will not receive the touch event. However if you return false from from IV's touch event, the lower down member RL will receive the touch event.
Hope this helps!
Just add these two listeners:
// Set an OnTouchListener to always return true for onTouch events so that a touch
// sequence cannot pass through the item to the item below.
view.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
v.onTouchEvent(event);
return true;
}
});
// Set an OnHoverListener to always return true for onHover events so that focus cannot
// pass through the item to the item below.
view.setOnHoverListener(new OnHoverListener() {
#Override
public boolean onHover(View v, MotionEvent event) {
v.onHoverEvent(event);
return true;
}
});
You could use databindings and consume the clicks like this:
android:onClick="#{() -> true}"
In C#, I use an empty delegate:
objectName.Click += delegate {};
I haven't encountered any problems with it but it does prevent clicks from filtering through to underlying controls.
you can also se the root click listener to null:
// Do not process clicks on other areas of this fragment
binding.root.setOnClickListener(null)
This works 100%.
It doesnt affect other listeners that are already set on the fragment's views.
I wrote a service class which creates a transparent view (LinearLayout).
If the user is holding down, some music starts to play.
This runs fine, but the problem is that if I am in the android menu or in another app, I am not able to press any buttons. (My view seems to don't let the TouchEvents pass)
I have tried a lot of things like returning false or stopping the interception, but I am still not able to press any gui controls when the service is running.
How do I correctly pass the TouchEvent back to the System?
#Override
public boolean onTouch(View v, MotionEvent event) {
v.getParent().requestDisallowInterceptTouchEvent(true);
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());
if(event.getAction() == MotionEvent.ACTION_DOWN) {
if(!mp.isPlaying())
mp.start();
}
if(event.getAction() == MotionEvent.ACTION_UP) {
mp.pause();
}
//View parent = (View) v.getParent().getParent();
//parent.onTouchEvent(event);
v.onTouchEvent(event);
return false;
}
This runs fine, but the problem is that if I am in the android menu or in another app, I am not able to press any buttons. (My view seems to don't let the TouchEvents pass)
This is by design.
How do I correctly pass the TouchEvent back to the System?
You don't. If you have the ability to see a touch event via a system window, that touch event will not be dispatched to another app. This prevents "tapjacking" attacks.
So hi there.
I have a simple Layout with 2 Views in it. Both have an onTouchListener attached.
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
System.out.println("Touching");
return false;
}
});
But when I open the application on my phone and touch the first view and do NOT relase my finger and touch the second view with another finger, the second view wont trigger the touch event. why is this so?
I think in this case both touches are passed to the first view as a multi-touch event. So this is one event but contains (I forgot the details) both touch positions.
In my application I want to add a simple animation to buttons and other views acting as buttons.
To do this I set a custom onTouchListener to all views and call startAnimation on them.
My onTouch method looks like this:
public boolean onTouch(View v, MotionEvent event) {
// Only show animation when enabled.
if (v.isEnabled()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.startAnimation(shrink);
break;
case MotionEvent.ACTION_UP:
v.startAnimation(grow);
break;
}
}
return v.onTouchEvent(event);
}
This works ok as the views are resized to a smaller size while the user presses the button and returns to the original size when the user releases the finger.
However for some reason other buttons that lie near the touched button also get the UP event so they get a small animation flicker as well.
Why is this, and more importantly, how do I prevent this annoying behaviour?
Edit: Just to be clear. The neighbour views are also registered to the same listener instance, but they are not touched by my finger.
The view to wich you have registered listener then should only get notified then also use following approach.
You can explicitly check what is id of View and then only start animation.
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.desired_view_id) {
// Only show animation when enabled.
if (v.isEnabled()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.startAnimation(shrink);
break;
case MotionEvent.ACTION_UP:
v.startAnimation(grow);
break;
}
}
}
return v.onTouchEvent(event);
}
The problem was using the same Animation instance on several views. Creating seperate instances for each view fixed the problem.