1. 程式人生 > >android開發遊記:RecyclerView無法新增onItemClickListener最佳的高效解決方案

android開發遊記:RecyclerView無法新增onItemClickListener最佳的高效解決方案

自從RecyclerView釋出以來,由於其高度的可互動性被廣泛使用。但是RecyclerView確沒有像ListView一樣提供onItemClickListener卻讓人比較難過,網上搜索了一番有不少解決方案,但是其本質都是通過給每個item新增onClickListener來模仿一個偽onItemClickListener,這種為每個item新增點選監聽的解決方案不用多想也知道是浪費效能的方法。能不能像ListView那樣使用一個監聽解決問題呢?

查閱RecyclerView的api發現雖然沒有提供onItemClickListener但是提供了addOnItemTouchListener方法:

RecyclerView.addOnItemTouchListener(OnItemTouchListener listener)

既然可以新增觸控監聽,那麼我們完全可以獲取觸控手勢來識別點選事件,然後通過觸控座標來判斷點選的是哪一個item,雖然聽起來比較複雜,但是sdk 的 api已經為我們實現了大部分方法,我們只需要實現介面幾行程式碼就可以搞定了。

下面先說一下使用方法,後面詳細介紹其實現原理:

如何使用

recyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerView) {
      @Override
public void onItemClick(RecyclerView.ViewHolder vh) { //item點選事件 } });

其中OnRecyclerItemClickListener是自定義的一個觸控監聽器,程式碼如下:
僅有短短十多行程式碼,sdk已經為我們實現了大部分功能

public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener{
    private GestureDetectorCompat mGestureDetector;
    private
RecyclerView recyclerView; public OnRecyclerItemClickListener(RecyclerView recyclerView){ this.recyclerView = recyclerView; mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(),new ItemTouchHelperGestureListener()); } @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { mGestureDetector.onTouchEvent(e); return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { mGestureDetector.onTouchEvent(e); } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { View child = recyclerView.findChildViewUnder(e.getX(), e.getY()); if (child!=null) { RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child); onItemClick(vh); } return true; } //長點選事件,本例不需要不處理 //@Override //public void onLongPress(MotionEvent e) { // View child = recyclerView.findChildViewUnder(e.getX(), e.getY()); // if (child!=null) { // RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child); // onItemLongClick(vh); // } //} public abstract void onItemClick(RecyclerView.ViewHolder vh); //public abstract void onItemLongClick(RecyclerView.ViewHolder vh); }

以上就是全部的程式碼了,看起來很少,其實包含的內容還是比較多的,下面詳細剖析下其實現原理:

實現原理

查閱api發現,RecyclerView提供了設定觸控監聽的方法,那麼我們定義一個類OnRecyclerItemClickListener實現OnItemTouchListener,我們需要實現其3個方法:

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }

其中第三個方法是處理觸控事件衝突的,跟我們沒關係不用管它,前兩個方法是不是很熟悉呢,這不就是View的事件分發機制裡面的事件攔截和事件處理的兩個方法嗎,引數裡為我們提供了觸控事件的資料MotionEvent,我們要做的就是去解析座標點和觸控規律來識別觸控手勢,然後獲取觸控的是哪一個item,再執行我們的回撥,聽起來很複雜,但是我前面已經說過了,sdk已經為我們實現了手勢的識別

GestureDetectorCompat 就是處理手勢的類:手勢探測器,它比GestureDetector能更好相容低版本的api,但使用方法是一致的,我們例項化一個手勢探測器:

mGestureDetector = new GestureDetectorCompat(context,new GestureListener(){...});

我們例項化手勢探測器的時候需要提供一個手勢監聽器:OnGestureListener,探測器識別出手勢後就會回撥手勢監聽器中對應的方法,我們就可以在回撥方法中做我們想做的事情了。

sdk為我們提供了兩個手勢監聽器:OnGestureListenerOnDoubleTapListener

OnGestureListener的回撥介面如下:

    //使用者按下螢幕就會觸發
    public boolean onDown(MotionEvent e);
    //如果是按下的時間超過瞬間,而且在按下的時候沒有鬆開或者是拖動的,那麼onShowPress就會執行
    public void onShowPress(MotionEvent e);
    //一次單獨的輕擊擡起操作,也就是輕擊一下螢幕,就是普通點選事件
    public boolean onSingleTapUp(MotionEvent e);
    //在螢幕上拖動事件
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
    //長按觸控式螢幕,超過一定時長,就會觸發這個事件
    public void onLongPress(MotionEvent e);
    //滑屏,使用者按下觸控式螢幕、快速移動後鬆開
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);

OnDoubleTapListener的回撥介面如下:

    //單擊事件。用來判定該次點選是SingleTap而不是DoubleTap,
    //如果連續點選兩次就是DoubleTap手勢,如果只點擊一次,
    //系統等待一段時間後沒有收到第二次點選則判定該次點選為SingleTap而不是DoubleTap,
    //然後觸發SingleTapConfirmed事件
    public boolean onSingleTapConfirmed(MotionEvent e);
    //雙擊事件
    public boolean onDoubleTap(MotionEvent e);
    //雙擊間隔中發生的動作。指觸發onDoubleTap以後,在雙擊之間發生的其它動作
    public boolean onDoubleTapEvent(MotionEvent e);

可以看出OnGestureListener主要回調各種單擊事件,而OnDoubleTapListener回撥各種雙擊事件。而我們需要處理的點選事件其實就是上面的:onSingleTapUp()

值得一提的是sdk 還提供了一個外部類SimpleOnGestureListener,這個類實現了上面兩個介面的所有方法,但全都是空實現,函式體裡什麼也沒寫,其中就是把上面兩個介面合併一下,給出預設的空實現,這樣繼承SimpleOnGestureListener的時候就不用實現每一個方法了,既然如此,那麼我們定義一個類去繼承它吧。

定義一個ItemTouchHelperGestureListener 繼承自SimpleOnGestureListener ,實現onSingleTapUp方法:

private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
      @Override
      public boolean onSingleTapUp(MotionEvent e) {
      }
}

到這裡,已經獲取到了RecyclerView的點選事件和觸控事件資料MotionEvent ,那麼我們怎麼知道點選的是哪一個item呢?RecyclerView已經為我們提供了這樣的方法:findChildViewUnder(),我們可以通過這個方法獲得點選的item,同時我們呼叫RecyclerView的另一個方法getChildViewHolder(),可以獲得該item的ViewHolder,最後再回調我們定義的虛方法onItemClick()就ok了,這樣我們就可以在外部實現該方法來獲得item的點選事件了:

@Override
public boolean onSingleTapUp(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
     if (child!=null) {
         RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
         onItemClick(vh);
     }
     return true;
}

覺得有用的話,下面有個頂可以點一下:)