天天看點

Android RecyclerView 基于SimpleOnItemTouchListener的改進

熟悉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時注冊監聽的控件,達到動态修改和多個控件排除的功能。

該示例隻是抛磚引玉,還有許多東西亟待完善,但大體思路就是這樣。