熟悉RecyclerView的朋友應該知道,RecyclerView本身沒有實作OnItemClick事件監聽的功能,但是它給了我們一個基本事件監聽OnItemTouchListener,官方給了一個基本實作SimpleOnItemTouchListener,但其實什麼也沒有處理:
public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener
{
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
在這裡我們先要了解這三個函數的作用:
onInterceptTouchEvent 當傳回true時,MotionEvent将被攔截在這一層,不會将MotionEvent傳遞給下一層view。
onTouchEvent 收到了onInterceptTouchEvent的處理後,無論傳回什麼,都會調用onTouchEvent進行處理。
onRequestDisallowInterceptTouchEvent 當有子View反對攔截MotionEvent的時候,會調用該方法。這裡用不上,因為我們不做攔截。
是以當一個觸摸事件來臨是,首先MotionEvent的action值是ACTION_DOWN,緊接着幾次事件是ACTION_MOVE,最後以ACTION_UP結束。我們可以用GestureDetectorCompat來識别這個event是短按、長按還是輕按兩下等等。
網上比較流行的實作RecyclerView的點選事件監聽,是在ACTION_DOWN到來時進行攔截,然後使用GestureDetectorCompat分析這個事件,最後做處理(當然那種在adaptor中添加監聽的方式不在這裡讨論,我個人不太建議用那種方式實作)。進行攔截的後果是,觸摸事件不能再對RecyclerView的子View産生效果。假如你的RecyclerView中有Button之類的,那麼他們不會觸發點選,顯然有相當大的局限性。
另外一種不做攔截,但是他們沒有做對其他控件的事件響應的過濾,這樣會導緻多餘的點選事件響應。
針對上面的情況,我對他們的方法做了一些改進,在不做攔截的情況下實作點選事件監聽。以下是主要的代碼:
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
if (mGestureDetector == null) {
initGestureDetector(rv);
}
mGestureDetector.onTouchEvent(e);
return false;
}
private boolean isTouchPointInView(View view, int x, int y) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int left = location[0];
int top = location[1];
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
if (view.isClickable() && y >= top && y <= bottom && x >= left
&& x <= right) {
return true;
}
return false;
}
private boolean isCatchID(ViewGroup viewGroup, int loopID, MotionEvent e){
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View viewchild = viewGroup.getChildAt(i);
long catchID = viewchild.getId();
long targetID = R.id.extra;
if(catchID == targetID){
ArrayList<View> touches = viewchild.getTouchables();
for(View tmpV : touches) {
if (isTouchPointInView(tmpV, (int) e.getRawX(), (int) e.getRawY())) {
return true;
}
}
}
else{
if(viewchild instanceof ViewGroup){
if(isCatchID((ViewGroup) viewchild, ++loopID, e)){
return true;
}
}
}
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
super.onTouchEvent(rv, e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.onRequestDisallowInterceptTouchEvent(disallowIntercept);
Log.d(Tags.debug, "Disallow event ID "+(clickID)+" "+disallowIntercept);
}
/**
* 初始化GestureDetector
*/
private void initGestureDetector(final RecyclerView recyclerView) {
mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new GestureDetector.SimpleOnGestureListener() { // 這裡選擇SimpleOnGestureListener實作類,可以根據需要選擇重寫的方法
/**
* 單擊事件
*/
@Override
public boolean onSingleTapUp(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if(childView instanceof ViewGroup){
if(isCatchID((ViewGroup)childView, 0, e)){
Log.d(Tags.debug, "====>Catch need act id");
return false;
}
}
if (childView != null && mListener != null) {
mListener.onItemClick(childView, recyclerView.getChildLayoutPosition(childView));
return true;
}
return false;
}
/**
* 長按事件
*/
@Override
public void onLongPress(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if(childView instanceof ViewGroup){
if(isCatchID((ViewGroup)childView, 0, e)){
Log.d(Tags.debug, "====>Catch need act id");
return;
}
}
if (childView != null && mListener != null) {
mListener.onItemLongClick(childView, recyclerView.getChildLayoutPosition(childView));
}
}
/**
* 輕按兩下事件
*/
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
int action = e.getAction();
if (action == MotionEvent.ACTION_UP) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if(childView instanceof ViewGroup){
if(isCatchID((ViewGroup)childView, 0, e)){
Log.d(Tags.debug, "====>Catch need act id");
return false;
}
}
if (childView != null && mListener != null) {
mListener.onItemDoubleClick(childView, recyclerView.getChildLayoutPosition(childView));
return true;
}
}
return false;
}
});
}
在這裡我以R.id.extra為例,當識别動作完成後,對子View進行周遊查找R.id.extra控件,并判斷觸摸坐标是否發生在該控件上。如果是,則不調用回調函數,防止多餘的觸發回調動作。在實際應用當中,我們可以在添加listener時注冊監聽的控件,達到動态修改和多個控件排除的功能。
該示例隻是抛磚引玉,還有許多東西亟待完善,但大體思路就是這樣。