I have a RecyclerView with a LinearLayoutManager and an Adapter:
#Override public int getItemViewType(int position) {
return position == 0? R.layout.header : R.layout.item;
}
Here's header.xml:
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="#dimen/header_height"
/>
What I want to achieve is to have a hole in the RecyclerView where I can click through to anything behind the RecyclerView. I tried a lot of combinations the following attributes to no avail:
android:background="#android:color/transparent"
android:background="#null"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:longClickable="false"
How could I make the first item transparent (allowing touch events to anything behind it) in the list, but let it still occupy the space?
You could set the OnClickListener/OnTouchListener of all of your "holes" to the parent of the view behind the RecyclerView and delegate any of the MotionEvent and touch events to that parent ViewGroup's touch handling.
Update by TWiStErRob:
class HeaderViewHolder extends RecyclerView.ViewHolder {
public HeaderViewHolder(View view) {
super(view);
view.setOnTouchListener(new OnTouchListener() {
#Override public boolean onTouch(View v, MotionEvent event) {
// http://stackoverflow.com/questions/8121491/is-it-possible-to-add-a-scrollable-textview-to-a-listview
v.getParent().requestDisallowInterceptTouchEvent(true); // needed for complex gestures
// simple tap works without the above line as well
return behindView.dispatchTouchEvent(event); // onTouchEvent won't work
}
});
There's nothing special needed in the XML, just plain views (none of the attributes in the question).
v.getParent() is the RecyclerView and that long method stops it from starting a scroll gesture while holding your finger on the "hole".
My "hole" view in the list also has a semi-transparent background, but that doesn't matter because we're hand-delivering the touches.
Related
I have to a RecyclerView (nested) inside the ViewHolder of another RecyclerView (parent). Both nested and parent have fixed heights. The nested is placed in the bottom of the parent.
If I use the parent with a LinearLayoutManager.VERTICAL and the nested with a LinearLayoutManager.HORIZONTAL everything works as expected, but if both RecyclerViews have the same orientation the nested can't hear the move event -- thus not being able to scroll.
What should I do in this case? How can I have both RecyclerViews scrolling Horizontally and be able to scroll the nested separately from the parent?
Thanks!
As in my case I have the nested in the bottom of the parent I used a custom RecyclerView.OnItemTouchListener and a custom LinearLayoutManager, both of them added to the parent.
In the LinearLayoutManager I created a method for enabling and disabling the scroll.
#Override
public boolean canScrollVertically() {
return isScrollEnabled && super.canScrollVertically();
}
#Override
public boolean canScrollHorizontally() {
return isScrollEnabled && super.canScrollHorizontally();
}
public void setScrollEnabled(boolean isScrollEnabled) {
this.isScrollEnabled = isScrollEnabled;
}
On the custom RecyclerView.OnItemTouchListener I started checking the area the user touched and disabled/enabled the parent's scrolling capability.
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
if (e.getY() > (viewHolderHeight - nestedRecyclerViewHeight)) {
mLayoutManager.setScrollEnabled(false);
} else {
mLayoutManager.setScrollEnabled(true);
}
return false;
}
What this is doing is: whenever the user touches an Item of the parent the system calls onInterceptTouchEvent and if the user touches the area where the nested should be it disables the scroll of the parent. And it enables it back if the touch happens outside of the nested area.
I searched a lot to reach this answer and actually mixed a few answers that used the Custom LinearLayoutManager for disabling the scroll of a recyclerview, and the RecyclerView.OnItemTouchListener for disabling any interaction on the RecyclerView.
Hope this helps anyone else with a similar problem.
I have an issue making 2 nested vertical RecyclerViews. I know this isn't a great pattern, but these are the application requests.
I have a parent RecyclerView, and when a card expands it should scroll to top and in the expanded part, I have another RecyclerView (a list of locations).
The problem is that I couldn't pass the scroll event from the parent, to the child RecyclerView. I read about the NestedScrollingChild interface and tried to enable nestedScrolling in child, but with no success.
Any suggestions?
I fixed it by adding the following code on my main RecyclerView adapter. Works perfectly.
holder.locationsList.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.v(TAG, "CHILD TOUCH");
// Disallow the touch request for parent scroll on touch of child view
v.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
I have a problem I am struggling with a while now.
I have a Layout with a Button and a container in it.
<FrameLayout ... >
<Button
android:id="#+id/button"
...
/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/overlayContainer"/>
</FrameLayout>
My goal is that as I long-press the button, I attach a custom view MyCustomViewto the container and keep the finger pressed.
All the following (ACTION_MOVE, ACTION_UP) events should then ideally be dispatched to and evaluated by MyCustomView.
MyCustomView works like a circular flyout menu: it overlays, dims the background, and shows some options. You then slide your pressed finger to the option, lift it up, and it triggers a result.
mButton.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
// attach custom view to overlayContainer
// simplified code for demonstration
overlayContainer.addView(new MyCustomView());
return true;
}
});
Right now I don't see any option to "steal" the ACTION_DOWN-Event (which is required to start the event flow to a view) from the Button as I'm above it.
Nor does it work to manually generate and dispatch a ACTION_DOWN-Event in MyCustomView as I attach it.
While researching I found this post here, it basically is the same requirement, but for iOS (also does not provide an elegant solution, other that an click capturing overlay view) ): How to preserve touch event after new view is added by long press
Note that I want to avoid some kind of global overlay over the main view, I would like the solution to be as pluggable and portable as possible.
Thanks for any suggestions.
To answer my own question after the hint in the comments:
I solved it using a bare stripped version of TouchDelegate (had to extend it, since it unfortunetaly is no interface - setTouchDelegate only accepts TouchDelegate (sub)classes. Not 100% clean, but works great.
public class CustomTouchDelegate extends TouchDelegate {
private View mDelegateView;
public CustomTouchDelegate(View delegateView) {
super(new Rect(), delegateView);
mDelegateView = delegateView;
}
public boolean onTouchEvent(MotionEvent event) {
return mDelegateView.dispatchTouchEvent(event);
}
}
Then in my onLongClick method:
mButton.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
// attach custom view to overlayContainer, simplified for demonstration
MyCustomView myMenuView = new MyCustomView()
mButton.setTouchDelegate(new CustomTouchDelegate(myMenuView));
// What's left out here is to mButton.setTouchDelegate = null,
// as soon as the temporary Overlay View is removed
overlayContainer.addView(myMenuView);
return true;
}
});
This way, all my ACTION_MOVE events from the Button are delegated to MyCustomView (and may or may not need some translation of the coordinates) - et voilĂ .
Thanks to pskink for the hint.
I have a one RelativeLayout and this layout is having nearly 10 views in it.
I have set OnTouchListener to this Layout and doing some work in it and returning true.
this listener is working fine when I touch the layout where there are no View (mean on Empty area). If I touch on child views of this layout, this listener is not firing...
and from the documentation, I understood that we can override onInterceptTouchEvent() by extending ViewGroup (so here RelativeLayout) and handle touch events before child views consume this event...
this will do a trick, but I need to modify many xml files where I need this functionality by replacing RelativeLayout with my CustomRelativeLayout.
so my question is:
is there any way to handle touch event for RelativeLayout (ofcourse ViewGroup) before child views in RelativeLayout consumes event? I don't want to extend RelativeLayout...
Try to override
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
of Activity.
This method is the first that process touch events.
But in this case you need to work with current coordinates of views
While not necessarily being ideal, this works well for smaller layouts that won't have dynamic content.
In xml, set android:clickable="false" to all descendants of the ViewGroup in the layout (including nested ViewGroups and their children). Each child that gets clicked on will then propagate the click to its parent, eventually getting to the ViewGroup's default touch handlers. Make sure to set root ViewGroup, where you want to get the click events as android:clickable="true" or viewgroup.setClickable(true)
If you add views dynamically, make sure to call view.setClickable(false); before adding it to the view hierarchy.
Try add onTouchListener to RelativeLayout
RelativeLayout rl = (RelativeLayout) findViewById( R.id.relativeLauout );
rl.setOnTouchListener( new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// do something
return false;
}
});
or use method onTouchEvent from Activity
#Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.onTouchEvent(event);
}
Try to return false in the onTouchEvent() method of child views when processing ACTION_DOWN event. So the forwarding touch events will not be sent to child views, aka event not consumed.
You can simply set in your xml:
android:clickable="true"
MediaController always shown on top of all views in the screen and do not pass clicks to below views until it is hidden, my issue is:
How to add button view on top of MediaController so it handle click events ?
OR
How to pass click events to below view?
Try overriding MediaController.dispatchTouchEvent()
It suits your task better, see my answer here for a detailed explanation why.
The code will go something like this:
public class MyMediaController extends MediaController {
...
private WeakReference<Button> mButton;
// TODO You'll have to call this whenever the layout might have been rebuilt
// i.e. in onCreate() of your activity (or in onConfigurationChanged() if you
// handle configuration changes yourself)
public void setButton(Button button) {
mButton = new WeakReference<Button>(button);
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
int iX = (int) event.getX();
int iY = (int) event.getY();
Button button = mButton.get();
if (button != null) {
Rect buttonHitRect = new Rect();
button.getHitRect(buttonHitRect);
if (buttonHitRect != null && buttonHitRect.contains(iX, iY)) {
// user hit the button, dispatch event to the button
button.dispatchTouchEvent(event);
return true;
}
}
// button did not get hit, pass the touch through
return super.dispatchTouchEvent(event)
}
}
Try this, let me know how it goes.
UPD Theres's a very similar question on this, it may have a better solution: Android MediaController intercepts all other touch events
When a view is overlapping another view then the hidden view will not get any touch event as the view on top consumes the touch event. If you want to the hidden view to receive the touch event then you have to manually pass the touch event from the top view to the hidden view.
Here there are two possibilities:
You want the touch event to be shared by both the view: In this case after passing the touch event to the hidden view indicate android that touch event has not been consumed by returning false from the onTouch() method of the top view.
You want the touch event to be handled by only the hidden view: In this case after passing the touch event to the hidden view indicate android that the touch event been consumed by returning true from the onTouch() method of the top view.
Here is a sample code for this:
btn.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch (View v, MotionEvent event)
{
list.onTouchEvent(event);
return true; // return true if touch event is consumed else return false
}
});
XML for this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#drawable/shape" >
<ListView
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" >
</ListView>
<Button
android:id="#+id/view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#88cc0000"
android:layout_alignParentTop="true"
android:onClick="showMe" />
</RelativeLayout>
Here Button is hiding the list view still the list will scroll as the touch event is passed to the below layout.
I hope this will help. :)