So here's my layout structure:
- RelativeLayout (full screen) - FrameLayout (full screen) - Button (in the middle of the screen) - RecyclerView (align parent bottom but with very big padding top to capture the scrolling also in the top of the screen, so it's basically acting like a full screen `RecyclerView`)
So yes, the views are overlapping each other.
As of the reason the RecyclerView
is the topmost view, it captures all the touch events on screen, and swallows them, preventing any touches to "go through it" to the underlying views below it. (Note: by underlying views, I don't mean to the RecyclerView
's children but the other rootview's
children)
I've read tons of stackoverflow posts regarding propagating touch events and preventing swallowing touch events, etc, and even though it seems pretty straightforward task to accomplish, I couldn't achieve the following effect:
I want my RecyclerView
to capture the touch event, so it will scroll or whatever. but I want the rootView
to think as that the RecyclerView
didn't capture the event, and continue pass it to its other children (rootview
' s children).
Here's What I've tried to do:
1. Overriding dispatchTouchEvent
of the RecyclerView
to do its logic and return false to act as it didn't dispatch its touch event so the rootview will keep iterating for touch through its child views.
@Override public boolean dispatchTouchEvent(MotionEvent ev) { super.dispatchTouchEvent(ev); return false; }
What happened:
The RecyclerView
still functions but still swallows all touch events (just the same as before)
2. Overriding onTouchEvent
of the RecyclerView
to do its logic and return false. (Note: I know it doesn't seem as it would be the solution but I tried)
@Override public boolean onTouchEvent(MotionEvent e) { super.onTouchEvent(e); return false; }
What happened:
Same result as in #1
I've made some more tweaks with the same idea, but they didn't work as well, so I'm kinda clueless right now and would love for help of you fellows!
3 Answers
Answers 1
I have two suggestions for you:
The first is simpler but assumes that you want to click on the Button
and it can appear above the RecyclerView
, if this is the case you can simply change the layout's order:
- RelativeLayout (full screen) - FrameLayout (full screen) - RecyclerView - Button (in the middle of the screen)
If you need something more general then it can be done like this:
recyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int [] pos = new int[2]; recyclerView.getLocationOnScreen(pos); Rect rect = new Rect(); button.getHitRect(rect); if (rect.contains((int) event.getX() + pos[0], (int) event.getY() + pos[1])) { button.onTouchEvent(event); } return false; } });
You will need to handle all your views separately, which is not as nice, but it works in my test.
Note: if you have added addOnItemTouchListener
to your RecyclerView
you will need to add the said functionality to onInterceptTouchEvent
(or to both places, depends on your items and RecyclerView
).
Answers 2
I'm not sure how efficient/valid/nice solution is this, but this is what comes to my mind: what if you listen for touch events in the root view (RelativeLayout
in your case), and dispatch the touch event to all the children of that layout except for the RecyclerView
?
public class MyRelativeLayout extends RelativeLayout { ... @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final boolean intercept = super.onInterceptTouchEvent(ev); // getChildCount() - 1, because `RecyclerView` is the last child for (int i = 0, size = getChildCount() - 1; i < size; i++) { View v = getChildAt(i); v.dispatchTouchEvent(MotionEvent.obtain(ev)); } return intercept; } }
This seems like it should work.
Answers 3
I don't quite understand your problem, however if the goal is to not allow RecyclerView
to consume any and all touch events, then simply set android:elevation="1dp"
for the FrameLayout
and Override
dispatchTouchEvent()
like so:
@Override public boolean dispatchTouchEvent(MotionEvent event) { Logger.log("CustomFrameLayout : dispatchTouchEvent"); boolean result = super.dispatchTouchEvent(event); ((View) getParent()).findViewById(R.id.recyclerView).dispatchTouchEvent(event); return result; // This part can be changed according to requirement. }
This may or may not have some undesired side effects, since I just came up with it on my own, however as much as I have checked it does not.
Cases I've tested:
- Scrolling in RecyclerView : Works.
- Clicks in RecyclerView Items : Works.
- Clicks in Children of FrameLayout : Works.
UPDATE :
Why is there any need for elevation for the FrameLayout?
android:elevation
provides a means for position of z-axis of any layout. As to why there is a need to do so, to answer that I'll have to delve deeper and bore you with lengthy explanation (There is already loads of stuff written on it) suffice it to say that MotionEvent
s trickle down Views following some basic rules: From root to its children, The child to first get the event first (to check if the event belongs to it) trickles down on the basis of their position in the x-y axis and then on their order in z-axis (Top one gets the event first).
So In your case when a MotionEvent
is detected it is passed to the RelativeLayout
(RootView), which has 3 children, Button
will be filtered because of the position. Now you have 2 Views
a FrameLayout
and RecyclerView
. A RelativeLayout
's bottom-most child comes on top of the z-axis, which means RecyclerView
event will be dispatched to it first (which will naturally consume the event). If however a case arose in which the event was not consumed (your first attempt), the event will be passed on to the next child View
until there are no more child View
s left at which point the parent (RelativeLayout
) will assume the event belongs to itself and call its own onInterceptTouchEvent()
. During this process if a View
has consumed the event all the subsequent calls will be passed to it directly (The reason you can only make one of the two either FrameLayout
or RecyclerView
work).
All of the above explanation means by the rulebook only one
View
will ever consume theevent
.
What I Did
I simply elevated the FrameLayout
so it would be the official receiver of the event. Overrode the dispatchTouchEvent()
and sent the same event to its neighboring RecyclerView
too, which thinks the event has been brought to it by regular mode.
Now after all that explanation
The comment // This part can be changed according to requirement.
A better and possibly error-less method would be
boolean result2 = ((View) getParent()).findViewById(R.id.recyclerView).dispatchTouchEvent(event); return result || result2;
So if any of the Views
have consumed the event the root will think that it has been consumed too.
0 comments:
Post a Comment