本文原地址 http://blog.csdn.net/liaoinstan/article/details/51200600 揭保,感謝原作者庶灿!
自從RecyclerView發(fā)布以來(lái)也搓,由于其高度的可交互性被廣泛使用倘潜。但是RecyclerView確沒(méi)有像ListView一樣提供onItemClickListener卻讓人比較難過(guò)漆际,網(wǎng)上搜索了一番有不少解決方案意蛀,但是其本質(zhì)都是通過(guò)給每個(gè)item添加onClickListener來(lái)模仿一個(gè)偽onItemClickListener耸别,這種為每個(gè)item添加點(diǎn)擊監(jiān)聽(tīng)的解決方案不用多想也知道是浪費(fèi)性能的方法。能不能像ListView那樣使用一個(gè)監(jiān)聽(tīng)解決問(wèn)題呢县钥?
查閱RecyclerView的api發(fā)現(xiàn)雖然沒(méi)有提供onItemClickListener但是提供了addOnItemTouchListener方法:
RecyclerView.addOnItemTouchListener(OnItemTouchListener listener)
既然可以添加觸摸監(jiān)聽(tīng)秀姐,那么我們完全可以獲取觸摸手勢(shì)來(lái)識(shí)別點(diǎn)擊事件,然后通過(guò)觸摸坐標(biāo)來(lái)判斷點(diǎn)擊的是哪一個(gè)item若贮,雖然聽(tīng)起來(lái)比較復(fù)雜省有,但是sdk 的 api已經(jīng)為我們實(shí)現(xiàn)了大部分方法,我們只需要實(shí)現(xiàn)接口幾行代碼就可以搞定了谴麦。下面先說(shuō)一下使用方法蠢沿,后面詳細(xì)介紹其實(shí)現(xiàn)原理:
如何使用
/**
* RecyclerView 的自定義點(diǎn)擊監(jiān)聽(tīng)事件。參考于:
* http://blog.csdn.net/liaoinstan/article/details/51200600
*
* author: jby
* created at 2016/7/27 15:01
*/
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
private GestureDetectorCompat gestureDetector;
private RecyclerView recyclerView;
public OnRecyclerItemClickListener(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
this.gestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener());
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
gestureDetector.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
gestureDetector.onTouchEvent(e);
}
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);
}
以上就是全部的代碼了匾效,看起來(lái)很少舷蟀,其實(shí)包含的內(nèi)容還是比較多的,下面詳細(xì)剖析下其實(shí)現(xiàn)原理:
實(shí)現(xiàn)原理
查閱api發(fā)現(xiàn)面哼,RecyclerView提供了設(shè)置觸摸監(jiān)聽(tīng)的方法野宜,那么我們定義一個(gè)類(lèi)OnRecyclerItemClickListener實(shí)現(xiàn)OnItemTouchListener,我們需要實(shí)現(xiàn)其3個(gè)方法:
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
其中第三個(gè)方法是處理觸摸事件沖突的精绎,跟我們沒(méi)關(guān)系不用管它速缨,前兩個(gè)方法是不是很熟悉呢,這不就是View的事件分發(fā)機(jī)制里面的事件攔截和事件處理的兩個(gè)方法嗎代乃,參數(shù)里為我們提供了觸摸事件的數(shù)據(jù)MotionEvent旬牲,我們要做的就是去解析坐標(biāo)點(diǎn)和觸摸規(guī)律來(lái)識(shí)別觸摸手勢(shì)仿粹,然后獲取觸摸的是哪一個(gè)item,再執(zhí)行我們的回調(diào)原茅,聽(tīng)起來(lái)很復(fù)雜吭历,但是我前面已經(jīng)說(shuō)過(guò)了,sdk已經(jīng)為我們實(shí)現(xiàn)了手勢(shì)的識(shí)別:
GestureDetectorCompat 就是處理手勢(shì)的類(lèi):手勢(shì)探測(cè)器擂橘,它比GestureDetector能更好兼容低版本的api晌区,但使用方法是一致的,我們實(shí)例化一個(gè)手勢(shì)探測(cè)器:
mGestureDetector = new GestureDetectorCompat(context,new GestureListener(){...});
我們實(shí)例化手勢(shì)探測(cè)器的時(shí)候需要提供一個(gè)手勢(shì)監(jiān)聽(tīng)器:OnGestureListener通贞,探測(cè)器識(shí)別出手勢(shì)后就會(huì)回調(diào)手勢(shì)監(jiān)聽(tīng)器中對(duì)應(yīng)的方法朗若,我們就可以在回調(diào)方法中做我們想做的事情了。
sdk為我們提供了兩個(gè)手勢(shì)監(jiān)聽(tīng)器:OnGestureListener昌罩,OnDoubleTapListener
OnGestureListener的回調(diào)接口如下:
//用戶按下屏幕就會(huì)觸發(fā)
public boolean onDown(MotionEvent e);
//如果是按下的時(shí)間超過(guò)瞬間哭懈,而且在按下的時(shí)候沒(méi)有松開(kāi)或者是拖動(dòng)的,那么onShowPress就會(huì)執(zhí)行
public void onShowPress(MotionEvent e);
//一次單獨(dú)的輕擊抬起操作,也就是輕擊一下屏幕茎用,就是普通點(diǎn)擊事件
public boolean onSingleTapUp(MotionEvent e);
//在屏幕上拖動(dòng)事件
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
//長(zhǎng)按觸摸屏遣总,超過(guò)一定時(shí)長(zhǎng),就會(huì)觸發(fā)這個(gè)事件
public void onLongPress(MotionEvent e);
//滑屏轨功,用戶按下觸摸屏旭斥、快速移動(dòng)后松開(kāi)
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
OnDoubleTapListener的回調(diào)接口如下:
//單擊事件。用來(lái)判定該次點(diǎn)擊是SingleTap而不是DoubleTap古涧,
//如果連續(xù)點(diǎn)擊兩次就是DoubleTap手勢(shì)垂券,如果只點(diǎn)擊一次,
//系統(tǒng)等待一段時(shí)間后沒(méi)有收到第二次點(diǎn)擊則判定該次點(diǎn)擊為SingleTap而不是DoubleTap蒿褂,
//然后觸發(fā)SingleTapConfirmed事件
public boolean onSingleTapConfirmed(MotionEvent e);
//雙擊事件
public boolean onDoubleTap(MotionEvent e);
//雙擊間隔中發(fā)生的動(dòng)作圆米。指觸發(fā)onDoubleTap以后,在雙擊之間發(fā)生的其它動(dòng)作
public boolean onDoubleTapEvent(MotionEvent e);
可以看出OnGestureListener主要回調(diào)各種單擊事件啄栓,而OnDoubleTapListener回調(diào)各種雙擊事件娄帖。而我們需要處理的點(diǎn)擊事件其實(shí)就是上面的:onSingleTapUp()
值得一提的是sdk 還提供了一個(gè)外部類(lèi)SimpleOnGestureListener,這個(gè)類(lèi)實(shí)現(xiàn)了上面兩個(gè)接口的所有方法昙楚,但全都是空實(shí)現(xiàn)近速,函數(shù)體里什么也沒(méi)寫(xiě),其中就是把上面兩個(gè)接口合并一下堪旧,給出默認(rèn)的空實(shí)現(xiàn)削葱,這樣繼承SimpleOnGestureListener的時(shí)候就不用實(shí)現(xiàn)每一個(gè)方法了,既然如此淳梦,那么我們定義一個(gè)類(lèi)去繼承它吧析砸。
定義一個(gè)ItemTouchHelperGestureListener 繼承自SimpleOnGestureListener ,實(shí)現(xiàn)onSingleTapUp方法:
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
}
}
到這里爆袍,已經(jīng)獲取到了RecyclerView的點(diǎn)擊事件和觸摸事件數(shù)據(jù)MotionEvent 首繁,那么我們?cè)趺粗傈c(diǎn)擊的是哪一個(gè)item呢作郭?RecyclerView已經(jīng)為我們提供了這樣的方法:findChildViewUnder(),我們可以通過(guò)這個(gè)方法獲得點(diǎn)擊的item弦疮,同時(shí)我們調(diào)用RecyclerView的另一個(gè)方法getChildViewHolder()夹攒,可以獲得該item的ViewHolder,最后再回調(diào)我們定義的虛方法onItemClick()就ok了胁塞,這樣我們就可以在外部實(shí)現(xiàn)該方法來(lái)獲得item的點(diǎn)擊事件了:
@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;
}
博文到這里就完了咏尝。
說(shuō)一點(diǎn)自己在使用的時(shí)候的感受:
這個(gè)觸摸方法能很高效地獲得RecyclerView的子Item對(duì)應(yīng)的ViewHolder。但是囿于自己的能力啸罢,拿到了ViewHolder之后编检,若是Item里面有更小的組件需要設(shè)置點(diǎn)擊事件,就得一個(gè)個(gè)單獨(dú)設(shè)置伺糠,比較麻煩蒙谓。自己也沒(méi)有想出好的辦法……待解決吧。训桶。。
希望可以幫到你~