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為我們提供了兩個手勢監聽器:OnGestureListener,OnDoubleTapListener
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;
}
覺得有用的話,下面有個頂可以點一下:)