RecyclerView的高效觸摸監(jiān)聽(tīng)

本文原地址 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)有想出好的辦法……待解決吧。训桶。。

希望可以幫到你~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酣倾,一起剝皮案震驚了整個(gè)濱河市舵揭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌躁锡,老刑警劉巖午绳,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異映之,居然都是意外死亡拦焚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)杠输,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赎败,“玉大人,你說(shuō)我怎么就攤上這事蠢甲〗┕危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鹦牛,是天一觀的道長(zhǎng)搞糕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)曼追,這世上最難降的妖魔是什么窍仰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮礼殊,結(jié)果婚禮上驹吮,老公的妹妹穿的比我還像新娘鲫忍。我一直安慰自己,他們只是感情好钥屈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布悟民。 她就那樣靜靜地躺著,像睡著了一般篷就。 火紅的嫁衣襯著肌膚如雪射亏。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天竭业,我揣著相機(jī)與錄音智润,去河邊找鬼。 笑死未辆,一個(gè)胖子當(dāng)著我的面吹牛窟绷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咐柜,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼兼蜈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拙友?” 一聲冷哼從身側(cè)響起为狸,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遗契,沒(méi)想到半個(gè)月后辐棒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牍蜂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年漾根,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鲫竞。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辐怕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贡茅,到底是詐尸還是另有隱情秘蛇,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布顶考,位于F島的核電站赁还,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏驹沿。R本人自食惡果不足惜艘策,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渊季。 院中可真熱鬧朋蔫,春花似錦罚渐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至青扔,卻和暖如春源织,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背微猖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工谈息, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人凛剥。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓侠仇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親犁珠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逻炊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評(píng)論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,754評(píng)論 22 665
  • 在iOS開(kāi)發(fā)中經(jīng)常會(huì)涉及到觸摸事件。本想自己總結(jié)一下盲憎,但是遇到了這篇文章嗅骄,感覺(jué)總結(jié)的已經(jīng)很到位,特此轉(zhuǎn)載饼疙。作者:L...
    WQ_UESTC閱讀 6,009評(píng)論 4 26
  • 好奇觸摸事件是如何從屏幕轉(zhuǎn)移到APP內(nèi)的?困惑于Cell怎么突然不能點(diǎn)擊了慕爬?糾結(jié)于如何實(shí)現(xiàn)這個(gè)奇葩響應(yīng)需求窑眯?亦或是...
    Lotheve閱讀 57,055評(píng)論 51 599
  • 早上,跟媽媽因?yàn)橐患唤?jīng)意的小事引起爭(zhēng)執(zhí)医窿,誰(shuí)成想磅甩,越演越烈,事不大姥卢,但情緒猶如決堤的山洪一發(fā)不可收拾……帶著一腔憤...
    小宇宙的猴年馬月閱讀 215評(píng)論 0 1