揭開RecyclerView的神秘面紗(二):處理RecyclerView的點(diǎn)擊事件

前言

上一篇文章揭開RecyclerView的神秘面紗(一):RecyclerView的基本使用中燕垃,主要講述了RecyclerView的基本使用方法裹纳,不同的布局管理器而造成的多樣化展示方式,展示了數(shù)據(jù)之后括儒,一般都會(huì)與用戶進(jìn)行交互,因此我們需要處理用戶的點(diǎn)擊事件楼咳。在ListView和GridView提供了onItemClickListener這個(gè)監(jiān)聽器娃弓,然而我們查找RecyclerView的API卻沒有類似的監(jiān)聽器,因此我們需要自己手動(dòng)處理它的點(diǎn)擊事件列敲。
以下提供兩種方法來實(shí)現(xiàn)處理RecyclerView點(diǎn)擊事件的功能阱佛,以下代碼均基于上一篇文章的代碼做出修改。

方法一:利用View.onClickListener及onLongClickListener

利用了java回調(diào)機(jī)制戴而,這里我們依賴于子Item View的onClickListener及onLongClickListener凑术。
首先對MyAdapter.java代碼做出如下修改
①新建兩個(gè)內(nèi)部接口:

    public interface OnItemClickListener{
        void onItemClick(View view,int position);
    }

    public interface OnItemLongClickListener{
        void onItemLongClick(View view,int position);
    }

②新建兩個(gè)私有變量用于保存用戶設(shè)置的監(jiān)聽器及其set方法:

    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;

    public void setOnItemClickListener(OnItemClickListener mOnItemClickListener){
        this.mOnItemClickListener = mOnItemClickListener;
    }
    
    public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
        this.mOnItemLongClickListener = mOnItemLongClickListener;
    }

③在onBindViewHolder方法內(nèi),實(shí)現(xiàn)回調(diào):

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
    holder.mTextView.setText(mDataSet.get(position));
    //判斷是否設(shè)置了監(jiān)聽器
    if(mOnItemClickListener != null){
        //為ItemView設(shè)置監(jiān)聽器
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getLayoutPosition(); // 1
                mOnItemClickListener.onItemClick(holder.itemView,position); // 2
            }
        });
    }
    if(mOnItemLongClickListener != null){
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                int position = holder.getLayoutPosition();
                mOnItemLongClickListener.onItemLongClick(holder.itemView,position);
                //返回true 表示消耗了事件 事件不會(huì)繼續(xù)傳遞
                return true;
            }
        });
    }
}

可以看到所意,這里實(shí)際上用到了子Item View的onClickListener和onLongClickListener這兩個(gè)監(jiān)聽器淮逊,如果當(dāng)前子item view被點(diǎn)擊了,會(huì)觸發(fā)點(diǎn)擊事件進(jìn)行回調(diào)扶踊,然后在①處獲取當(dāng)前點(diǎn)擊位置的position值泄鹏,接著在②號代碼處進(jìn)行再次回調(diào),而這一次的回調(diào)是我們自己手動(dòng)添加的秧耗,需要實(shí)現(xiàn)上面所述的接口备籽。
修改完MyAdapter.java后,我們接著在MainActivity.java中設(shè)置監(jiān)聽器分井,采用匿名內(nèi)部類的形式實(shí)現(xiàn)了onItemClickListener胶台、onItemLongClickListener接口,這種寫法與一般的設(shè)置監(jiān)聽器的流程相同:

    
mAdapter = new MyAdapter(mData);
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MainActivity.this, "click " + mData.get(position), Toast.LENGTH_SHORT).show();
    }
});
mAdapter.setOnItemLongClickListener(new MyAdapter.OnItemLongClickListener() {
    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MainActivity.this,"long click "+mData.get(position),Toast.LENGTH_SHORT).show();
    }
});
mRecyclerView.setAdapter(mAdapter);

我們運(yùn)行程序杂抽,觀察一下運(yùn)行結(jié)果:


點(diǎn)擊Item 1.png

長按 Item 2.png

方法二:利用RecyclerView.OnItemTouchListener

官方雖然沒有提供現(xiàn)成的監(jiān)聽器诈唬,但是提供了一個(gè)內(nèi)部接口:OnItemTouchListener,我們看看官方文檔對它的描述:An OnItemTouchListener allows the application to intercept touch events in progress at the view hierarchy level of the RecyclerView before those touch events are considered for RecyclerView's own scrolling behavior缩麸。大概意思是說該接口允許我們對RecyclerView的觸摸事件進(jìn)行攔截铸磅,我們看看它的幾個(gè)接口方法:

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return false;
    }
    
    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }
    
    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }

熟悉View事件分發(fā)機(jī)制的同學(xué)看到這些方法的名字一定覺得很熟悉了吧,所以這個(gè)接口的實(shí)質(zhì)是利用了View的事件分發(fā)與攔截機(jī)制杭朱。大體思路是:我們可以在onInterceptTouchEvent獲取當(dāng)前觸摸位置對應(yīng)的子item view阅仔,根據(jù)點(diǎn)擊狀態(tài)決定是否要把事件攔截,在攔截的時(shí)候同時(shí)添加一個(gè)回調(diào)方法弧械,這樣我們自己實(shí)現(xiàn)的監(jiān)聽器接口就能在這里得到回調(diào)八酒。具體的請看如下代碼:
新建一個(gè)RecyclerViewClickListener.java:

/**
  *  @author ChenYu
  *      2016-05-10
  */
public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {

    private int mLastDownX,mLastDownY;
    //該值記錄了最小滑動(dòng)距離
    private int touchSlop ;
    private OnItemClickListener mListener;
    //是否是單擊事件
    private boolean isSingleTapUp = false;
    //是否是長按事件
    private boolean isLongPressUp = false;
    private boolean isMove = false;
    private long mDownTime;

    //內(nèi)部接口,定義點(diǎn)擊方法以及長按方法
    public interface OnItemClickListener {
        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }

    public RecyclerViewClickListener(Context context,OnItemClickListener listener){
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mListener = listener;
    }
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        int x = (int) e.getX();
        int y = (int) e.getY();
        switch (e.getAction()){
            /**
             *  如果是ACTION_DOWN事件刃唐,那么記錄當(dāng)前按下的位置羞迷,
             *  記錄當(dāng)前的系統(tǒng)時(shí)間界轩。
             */
            case MotionEvent.ACTION_DOWN:
                mLastDownX = x;
                mLastDownY = y;
                mDownTime = System.currentTimeMillis();
                isMove = false;
                break;
            /**
             *  如果是ACTION_MOVE事件,此時(shí)根據(jù)TouchSlop判斷用戶在按下的時(shí)候是否滑動(dòng)了衔瓮,
             *  如果滑動(dòng)了浊猾,那么接下來將不處理點(diǎn)擊事件
             */
            case MotionEvent.ACTION_MOVE:
                if(Math.abs(x - mLastDownX)>touchSlop || Math.abs(y - mLastDownY)>touchSlop){
                    isMove = true;
                }
                break;
            /**
             *  如果是ACTION_UP事件,那么根據(jù)isMove標(biāo)志位來判斷是否需要處理點(diǎn)擊事件热鞍;
             *  根據(jù)系統(tǒng)時(shí)間的差值來判斷是哪種事件葫慎,如果按下事件超過1s,則認(rèn)為是長按事件薇宠,
             *  否則是單擊事件偷办。
             */
            case MotionEvent.ACTION_UP:
                if(isMove){
                    break;
                }
                if(System.currentTimeMillis()-mDownTime > 1000){
                    isLongPressUp = true;
                }else {
                    isSingleTapUp = true;
                }
                break;
        }
        if(isSingleTapUp ){
            //根據(jù)觸摸坐標(biāo)來獲取childView
            View childView = rv.findChildViewUnder(e.getX(),e.getY());
            isSingleTapUp = false;
            if(childView != null){
                //回調(diào)mListener#onItemClick方法
                mListener.onItemClick(childView,rv.getChildLayoutPosition(childView));
                return true;
            }
            return false;
        }
        if (isLongPressUp ){
            View childView = rv.findChildViewUnder(e.getX(),e.getY());
            isLongPressUp = false;
            if(childView != null){
                mListener.onItemLongClick(childView, rv.getChildLayoutPosition(childView));
                return true;
            }
            return false;
        }
        return false;
    }

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

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

接著我們在MainActivity.java添加一段代碼,同時(shí)不要忘記把方法一的代碼注釋掉哦:

 //調(diào)用RecyclerView#addOnItemTouchListener方法能添加一個(gè)RecyclerView.OnItemTouchListener對象
 mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this,new RecyclerViewClickListener.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MainActivity.this,"Click "+mData.get(position),Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MainActivity.this,"Long Click "+mData.get(position),Toast.LENGTH_SHORT).show();
    }
}));

OK,編譯通過運(yùn)行程序澄港,結(jié)果和上面一模一樣爽篷。

那么方法一和方法二有何區(qū)別呢?
首先慢睡,方法一我們是直接在MyAdapter數(shù)據(jù)適配器中逐工,為itemview設(shè)置了內(nèi)置監(jiān)聽器,再通過這個(gè)監(jiān)聽器實(shí)現(xiàn)我們的回調(diào)方法漂辐,相當(dāng)于回調(diào)了兩次泪喊,同時(shí)這個(gè)方法與MyAdapter的耦合度比較高,也違反了單一職責(zé)原則髓涯,當(dāng)然其簡易性也是突出的優(yōu)點(diǎn)袒啼。而方法二,我們利用了onTouchListener接口對事件進(jìn)行了攔截纬纪,在攔截中處理我們的點(diǎn)擊事件蚓再,實(shí)現(xiàn)了與適配器的解耦,但是復(fù)雜程度會(huì)比方法一大包各≌觯總地來說,如果RecyclerView需要處理的點(diǎn)擊事件邏輯很簡單问畅,那么可以使用方法一娃属;如果需要處理比較復(fù)雜的點(diǎn)擊事件,比如說护姆,雙擊矾端、長按等點(diǎn)擊事件,則需要使用方法二去實(shí)現(xiàn)各種復(fù)雜的邏輯卵皂。

對方法二的優(yōu)化

在實(shí)現(xiàn)方法二的RecyclerViewClickListener的時(shí)候秩铆,在內(nèi)部對事件的實(shí)現(xiàn)了單擊、長按的判斷灯变,但是這個(gè)長按事件不是標(biāo)準(zhǔn)的殴玛,只有松開手指的時(shí)候才會(huì)觸發(fā)長按事件捅膘,這也算是一點(diǎn)瑕疵,同時(shí)如果要增加別的事件族阅,比如說雙擊事件篓跛,則需要增加相應(yīng)的邏輯膝捞,如果需要判斷的事件種類變多則會(huì)給我們的代碼編寫帶來困難坦刀,那么有沒有更加簡便的方法呢?其實(shí)安卓SDK為我們提供了一個(gè)手勢檢測類:GestureDetector來處理各種不同的手勢蔬咬,那么我們完全可以利用GestureDetector來對方法二進(jìn)行改進(jìn)鲤遥。

新建RecyclerViewClickListener2.java:

public class RecyclerViewClickListener2 implements RecyclerView.OnItemTouchListener {

    private GestureDetector mGestureDetector;
    private OnItemClickListener mListener;

    //內(nèi)部接口,定義點(diǎn)擊方法以及長按方法
    public interface OnItemClickListener {
        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }

    public RecyclerViewClickListener2(Context context, final RecyclerView recyclerView,OnItemClickListener listener){
        mListener = listener;
        mGestureDetector = new GestureDetector(context,
                new GestureDetector.SimpleOnGestureListener(){ //這里選擇SimpleOnGestureListener實(shí)現(xiàn)類林艘,可以根據(jù)需要選擇重寫的方法
                    //單擊事件
                    @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 onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        //把事件交給GestureDetector處理
        if(mGestureDetector.onTouchEvent(e)){
            return true;
        }else
            return false;
    }

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

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }
}

在MainActivity.java改動(dòng)如下:

mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener2(this, mRecyclerView,
        new RecyclerViewClickListener2.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MainActivity.this,"Click "+mData.get(position),Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MainActivity.this,"Long Click "+mData.get(position),Toast.LENGTH_SHORT).show();
    }
}));

運(yùn)行結(jié)果與上面的相同盖奈。

至此,關(guān)于處理RecyclerView的點(diǎn)擊事件的方法講述完畢狐援,如果你們還有什么別的方法歡迎留言討論钢坦。

更多閱讀:

揭開RecyclerView的神秘面紗(一):RecyclerView的基本使用
揭開RecyclerView的神秘面紗(三):操作數(shù)據(jù)及添加分割線

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市啥酱,隨后出現(xiàn)的幾起案子爹凹,更是在濱河造成了極大的恐慌,老刑警劉巖镶殷,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禾酱,死亡現(xiàn)場離奇詭異,居然都是意外死亡绘趋,警方通過查閱死者的電腦和手機(jī)颤陶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陷遮,“玉大人滓走,你說我怎么就攤上這事∶辈觯” “怎么了闲坎?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腰懂。 經(jīng)常有香客問我,道長项秉,這世上最難降的妖魔是什么绣溜? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮娄蔼,結(jié)果婚禮上怖喻,老公的妹妹穿的比我還像新娘底哗。我一直安慰自己,他們只是感情好锚沸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布跋选。 她就那樣靜靜地躺著,像睡著了一般哗蜈。 火紅的嫁衣襯著肌膚如雪前标。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天距潘,我揣著相機(jī)與錄音炼列,去河邊找鬼。 笑死音比,一個(gè)胖子當(dāng)著我的面吹牛俭尖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洞翩,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼稽犁,長吁一口氣:“原來是場噩夢啊……” “哼骚亿!你這毒婦竟也來了已亥?” 一聲冷哼從身側(cè)響起循未,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎的妖,沒想到半個(gè)月后绣檬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娇未,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年星虹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了零抬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宽涌,死狀恐怖平夜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情忽妒,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布段直,位于F島的核電站,受9級特大地震影響决侈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赖歌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一蹂楣、第九天 我趴在偏房一處隱蔽的房頂上張望讯蒲。 院中可真熱鬧痊土,春花似錦墨林、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隙袁。三九已至弃榨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲸睛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工箱舞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拳亿。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓晴股,卻偏偏與公主長得像肺魁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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