1. 程式人生 > >RecyclerView的Item點選事件實現總結

RecyclerView的Item點選事件實現總結

自從開始使用RecyclerView代替ListView,會發現有很多地方需要學習。前一段時間的學習記錄有:

實現 RecyclerView的Item的點選事件有三種方式:

  1. 在建立 ItemView時新增點選監聽

  2. ItemView attach RecyclerView時實現

  3. 通過RecyclerView已有的方法addOnItemTouchListener()實現

1.在建立ItemView時新增點選監聽

思路是:因為ViewHolder我們可以拿到每個Item的根佈局,所以如果我們為根佈局設定單獨的OnClick監聽並將其開放給Adapter,那不就可以在組裝RecyclerView時就能夠設定ItemClickListener,只不過這個Listener不是設定到RecyclerView上而是設定到Adapter。具體實現程式碼如下:

public class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.SampleViewHolder> {

    private List<DataBean> mDatas;
    private OnItemClickListener mListener; // Item點選事件

    public DataBean getItem(int position) {
        return mDatas == null ? null : mDatas.get(position);
    }

    @Override
    public SampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent,false);
        return new SampleViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(SampleViewHolder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return mDatas == null ? 0 : mDatas.size();
    }

    class SampleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

        public SampleViewHolder(View itemView) {
            super(itemView);
            // TODO:初始化View
            ...

            itemView.setOnClickListener(this);
            itemView.setOnLongClickListener(this);
        }

        @Override
        public void onClick(View v) {
            if (mListener != null) {
                mListener.onItemClick(SampleAdapter.this, v, getLayoutPosition());
            }
        }

        @Override
        public boolean onLongClick(View v) {
            if (mListener != null) {
                mListener.onItemLongClick(SampleAdapter.this, v, getLayoutPosition());
                return true;
            }
            return false;
        }
    }
}

2.當ItemView attach RecyclerView時實現

實現的程式碼如下

public class ItemClickSupport {

    private static final int KEY = 0x99999999;
    private final RecyclerView mRecyclerView;
    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;

    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mOnItemClickListener != null) {
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                mOnItemClickListener.onItemClicked(mRecyclerView, v, holder.getAdapterPosition());
            }
        }
    };

    private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            if (mOnItemLongClickListener != null) {
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, v, holder.getAdapterPosition());
            }
            return false;
        }
    };

    private RecyclerView.OnChildAttachStateChangeListener mAttachListener = new RecyclerView.OnChildAttachStateChangeListener() {

        @Override
        public void onChildViewAttachedToWindow(View view) {
            if (mOnItemClickListener != null) {
                view.setOnClickListener(mOnClickListener);
            }
            if (mOnItemLongClickListener != null) {
                view.setOnLongClickListener(mOnLongClickListener);
            }
        }

        @Override
        public void onChildViewDetachedFromWindow(View view) {
        }
    };

    /**
     * ItemClickSupport的私有構造方法
     */
    private ItemClickSupport(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        mRecyclerView.setTag(KEY, this);
        // 為RecyclerView設定OnChildAttachStateChangeListener事件監聽
        mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
    }

    /**
     * 為RecyclerView設定ItemClickSupport
     */
    public static ItemClickSupport addTo(RecyclerView view) {
        ItemClickSupport support = (ItemClickSupport) view.getTag(KEY);
        if (support == null) {
            support = new ItemClickSupport(view);
        }
        return support;
    }

    /**
     * 為RecyclerView移除ItemClickSupport
     */
    public static ItemClickSupport removeFrom(RecyclerView view) {
        ItemClickSupport support = (ItemClickSupport) view.getTag(KEY);
        if (support != null) {
            support.detach(view);
        }
        return support;
    }

    /**
     * 為RecyclerView設定點選事件監聽
     */
    public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
        return this;
    }

    /**
     * 為RecyclerView設定長按事件監聽
     */
    public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
        mOnItemLongClickListener = listener;
        return this;
    }

    /**
     * 為RecyclerView移除OnChildAttachStateChangeListener事件監聽
     */
    private void detach(RecyclerView view) {
        view.removeOnChildAttachStateChangeListener(mAttachListener);
        view.setTag(KEY, null);
    }

    /**
     * RecyclerView的點選事件監聽介面
     */
    public interface OnItemClickListener {
        void onItemClicked(RecyclerView recyclerView, View itemView, int position);
    }

    /**
     * RecyclerView的長按事件監聽介面
     */
    public interface OnItemLongClickListener {
        boolean onItemLongClicked(RecyclerView recyclerView, View itemView, int position);
    }
}

上面的程式碼中給RecyclerView設定了OnChildAttachStateChangeListener事件監聽當子View attach RecyclerView時設定事件監聽

private RecyclerView.OnChildAttachStateChangeListener mAttachListener = new RecyclerView.OnChildAttachStateChangeListener() {

 @Override
    public void onChildViewAttachedToWindow(View view) {
        if (mOnItemClickListener != null) {
            view.setOnClickListener(mOnClickListener);
        }
        if (mOnItemLongClickListener != null) {
            view.setOnLongClickListener(mOnLongClickListener);
        }
    }
 
    @Override
    public void onChildViewDetachedFromWindow(View view) {}
};
使用時只需要呼叫addTo(RecycleView view)方法得到ItemClickSupport物件,然後呼叫setOnItemClickListener()方法和setOnItemLongClickListener()方法設定ItemView的點選事件和長按事件監聽即可。

3.通過RecyclerView已有的方法addOnItemTouchListener()實現

3.1、檢視原始碼

檢視RecyclerView原始碼可以看到,RecyclerView預留了一個Item的觸控事件方法

/**
 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
 * to child views or this view's standard scrolling behavior.
 *
 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
 * returns true from
 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
 * for each incoming MotionEvent until the end of the gesture.</p>
 *
 * @param listener Listener to add
 * @see SimpleOnItemTouchListener
 */
public void addOnItemTouchListener(OnItemTouchListener listener) {
    mOnItemTouchListeners.add(listener);
}
通過註釋我們可知,此方法是在滾動事件之前呼叫需要傳入一個OnItemTouchListener物件OnItemTouchListener的程式碼如下
public static interface OnItemTouchListener { 
 
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
 
    public void onTouchEvent(RecyclerView rv, MotionEvent e);
 
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
此介面還提供了一個實現類且官方推薦使用該實現類SimpleOnItemTouchListener:
/**
 * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies and
 * default return values.
 * 
 * You may prefer to extend this class if you don't need to override all methods. Another
 * benefit of using this class is future compatibility. As the interface may change, we'll
 * always provide a default implementation on this class so that your code won't break when
 * you update to a new version of the support library.
 */
public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
       @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return false;
    }
 
    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    }
 
    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }
}
在觸控介面中,當觸控時會回撥一個MotionEvent物件,通過使用GestureDetectorCompat來解析使用者的操作。

3.2、瞭解GestureDetector的工作原理

對於觸控式螢幕,其原生的訊息無非按下、擡起、移動這幾種,我們只需要簡單過載onTouch或者設定觸控偵聽器setOnTouchListener即可進行處理。不過,為了提高我們的APP的使用者體驗,有時候我們需要識別使用者的手勢,Android給我們提供的手勢識別工具GestureDetector就可以幫上大忙了。

       GestureDetector的工作原理是,當我們接收到使用者觸控訊息時,將這個訊息交給GestureDetector去加工,我們通過設定偵聽器獲得GestureDetector處理後的手勢。

GestureDetector提供了兩個偵聽器介面,OnGestureListener處理單擊類訊息,OnDoubleTapListener處理雙擊類訊息。

OnGestureListener的介面有這幾個:

    // 單擊,觸控式螢幕按下時立刻觸發  
    abstract boolean onDown(MotionEvent e);  
    // 擡起,手指離開觸控式螢幕時觸發(長按、滾動、滑動時,不會觸發這個手勢)  
    abstract boolean onSingleTapUp(MotionEvent e);  
    // 短按,觸控式螢幕按下後片刻後擡起,會觸發這個手勢,如果迅速擡起則不會  
    abstract void onShowPress(MotionEvent e);  
    // 長按,觸控式螢幕按下後既不擡起也不移動,過一段時間後觸發  
    abstract void onLongPress(MotionEvent e);  
    // 滾動,觸控式螢幕按下後移動  
    abstract boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);  
    // 滑動,觸控式螢幕按下後快速移動並擡起,會先觸發滾動手勢,跟著觸發一個滑動手勢  
    abstract boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);  
OnDoubleTapListener的介面有這幾個:
    // 雙擊,手指在觸控式螢幕上迅速點選第二下時觸發  
    abstract boolean onDoubleTap(MotionEvent e);  
    // 雙擊的按下跟擡起各觸發一次  
    abstract boolean onDoubleTapEvent(MotionEvent e);  
    // 單擊確認,即很快的按下並擡起,但並不連續點選第二下  
    abstract boolean onSingleTapConfirmed(MotionEvent e);  

有時候我們並不需要處理上面所有手勢,方便起見,Android提供了另外一個類SimpleOnGestureListener實現瞭如上介面,我們只需要繼承SimpleOnGestureListener然後過載需要的手勢即可。

3.3實現點選事件監聽

瞭解GestureDetector的工作原理之後,便開始實現RecycleView的Item的點選事件。首先寫一個SimpleRecycleViewItemClickListener類繼承SimpleOnItemTouchListener構造時傳入Item點選回撥OnItemClickListener,並覆寫父類的boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)方法具體程式碼如下

/**
 * RecyclerView的Item點選事件監聽
 *
 * @author liyunlong
 * @date 2016/11/21 9:42
 */
public class SimpleRecycleViewItemClickListener extends RecyclerView.SimpleOnItemTouchListener {

    private OnItemClickListener mListener;
    private GestureDetectorCompat mGestureDetector;

    public SimpleRecycleViewItemClickListener(OnItemClickListener listener) {
        this.mListener = listener;
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (mGestureDetector == null) {
            initGestureDetector(rv);
        }
        if (mGestureDetector.onTouchEvent(e)) { // 把事件交給GestureDetector處理
            return true;
        } else {
            return false;
        }
    }

    /**
     * 初始化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 != 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 != 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 != null && mListener != null) {
                        mListener.onItemDoubleClick(childView, recyclerView.getChildLayoutPosition(childView));
                        return true;
                    }
                }
                return false;
            }

        });

    }

    /**
     * RecyclerView的Item點選事件監聽介面
     *
     * @author liyunlong
     * @date 2016/11/21 9:43
     */
    public interface OnItemClickListener {

        /**
         * 當ItemView的單擊事件觸發時呼叫
         */
        void onItemClick(View view, int position);

        /**
         * 當ItemView的長按事件觸發時呼叫
         */
        void onItemLongClick(View view, int position);

        /**
         * 當ItemView的雙擊事件觸發時呼叫
         */
        void onItemDoubleClick(View view, int position);
    }


    /**
     * RecyclerView的Item點選事件監聽實現
     *
     * @author liyunlong
     * @date 2016/11/21 10:05
     */
    public class SimpleOnItemClickListener implements OnItemClickListener {

        @Override
        public void onItemClick(View view, int position) {

        }

        @Override
        public void onItemLongClick(View view, int position) {

        }

        @Override
        public void onItemDoubleClick(View view, int position) {

        }
    }
}

在GestureDetectorCompat的手勢回撥中我們覆寫

  1. boolean onSingleTapUp(MotionEvent e):單擊事件回撥

  2. void onLongPress(MotionEvent e):長按事件回撥

  3. boolean onDoubleTapEvent(MotionEvent e):雙擊事件回撥

如果我們只需要監聽單擊事件,而不需要監聽長按事件和雙擊事件,構造SimpleRecycleViewItemClickListener時只需要傳入SimpleOnItemClickListener即可,如果需要處理其它的手勢監聽,也可以覆寫對應的手勢回撥方法。

4.三種方法對比

以上三種方式分別是

  1. 在建立ItemView時新增點選監聽

  2. ItemView attach RecyclerView時實現

  3. 通過RecyclerView已有的方法addOnItemTouchListener()實現

從以上三種方式的實現過程可知:

  1. 三種均可實現ItemView的點選事件和長按事件的監聽

  2. 種和第種方式可以很方便對ItemView中的子View進行監聽

  3. 種方式可以很方便獲取使用者點選的座標

  4. 種方式和第三種方式可以寫在單獨的類中相對於第種寫在Adapter的方式可使程式碼更獨立整潔。

綜上所述:

如果你只想監聽ItemView的點選事件或長按事件三種方式均可

如果你想監聽ItemView中每個子View的點選事件採用第種或者第種比較方便。