I have an ordinary RecyclerView
, and on top of it a transparent View
that implements GestureListener
, which basically have the same size of the RecyclerView
.
The GestureListener
will listen to scroll and fling gestures, and pass this MotionEvent
to the RecyclerView
underneath it.
I have already made the RecyclerView
able to scroll and fling. However, I can't find a way to pass a click event down to the RecyclerView
's items as well.
I already know that this is because ACTION_DOWN
is consumed in the GestureListener
. In fact, GestureListener
has a onSingleTap()
method for you to override, and this method was called whenever I perform a click.
According to this post, I tried to set an OnTouchListener
to my itemView and listen to ACTION_UP
events. However, the onTouch()
method is never called.
Below is how I do it:
1. Create a callback in the transparent GestureListener
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (scrollDetector == null) return false;
scrollDetector.onSingleTap(e);
return true;
}
Configure the callback in the activity, and pass the MotionEvent
to the RecyclerView
@Override
public void onSingleTap(MotionEvent e) {
mRecyclerView.onTouchEvent(e);
}
Set OnTouchListener
to the itemView in the adapter:
itemView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
v.performClick();
return true;
}
return false;
}
});
Using debugger, I can see that mRecyclerView.onTouchEvent(e)
was called; but the onTouch()
of itemView was not called.
So... How should I correctly pass the MotionEvent
to the itemView?
You may ask - "Why do you place a GestureListener on top of the RecyclerView?"
This is because I need to change the height of the RecyclerView
when the RecyclerView
is scrolled. However, if I do this using RecyclerView
's addOnScrollListener
, the value of dy
will fluctuate between negative and positive values, because dy is affected by its height as well. And the fluctuation will also be reflected to the UI.
Therefore I need a scroll detector that does not change its height when scrolled, and just pass the scroll and fling values to RecyclerView
by programmatically calling scrollBy()
and fling()
.
Stupid me. I should use dispatchTouchEvent(MotionEvent e)
instead of onTouchEvent(MotionEvent e)
.
However, this is not enough.
Simply calling dispatchTouchEvent(e)
using the MotionEvent from GestureListener is not working, because that e
is an ACTION_UP
event.
To simulate a click, you need both ACTION_DOWN
and ACTION_UP
.
And itemView does not need to set OnTouchListener
since you have already simulate
Code:
@Override
public void onSingleTap(MotionEvent e) {
long downTime = SystemClock.uptimeMillis();
long upTime = downTime + 100;
MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
e.getX(), e.getY(), 0);
mRecyclerView.dispatchTouchEvent(downEvent);
MotionEvent upEvent = MotionEvent.obtain(upTime, upTime, MotionEvent.ACTION_UP,
e.getX(), e.getY(), 0);
mRecyclerView.dispatchTouchEvent(upEvent);
downEvent.recycle();
upEvent.recycle();
}