RecyclerView --onItemClick設(shè)置匯總

眾所周知,RecyclerView是繼承自ViewGroup的,而不是像ListView一樣繼承自AbsListview.所以RecyclerView沒有OnItemClickListener,沒有OnItemLongClickListener,更沒有OnItemSelectedListener.所以這都要我們自己實(shí)現(xiàn)。What?。。?Are you kidding me? 嚴(yán)肅臉,沒錯(cuò),就是要我們自己來實(shí)現(xiàn)。實(shí)現(xiàn)的方法有很多種,我這里做了一下總結(jié),接下來我們一一列舉出來并且對(duì)比一下,當(dāng)然最后選哪一個(gè)還是看你自己喜歡咯。

  • 修改RecyclerView的源碼,在ViewHolder里面添加監(jiān)聽。
  1. 首先在RecyclerView里面添加OnItemClickListenr接口,并且添加OnItemClickListener的成員變量以及set方法如下:
  private OnItemClickListener onItemClickListener;
  public interface OnItemClickListener {  
        void onItemClick(View view, int position);  
  }  
  
  public void setOnItemClickListener(OnItemClickListener listener) {  
        mOnItemClickListener = listener;  
    }  
  

然后再RecyclerView的抽象類ViewHolder里面的構(gòu)造方法里面添加如下代碼。

  public ViewHolder(View itemView) {  
            if (itemView == null) {  
                throw new IllegalArgumentException("itemView may not be null");  
            }  
            this.itemView = itemView;  
            this.itemView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                   //getChildLayoutPosition(v),根據(jù)v來獲取v的位置。 mOnItemClickListener.onItemClick(v, getChildLayoutPosition(v))
                }
            }); //添加這一句就可以添加onclick事件了。
        }  
  

這個(gè)方法雖然可行,但是需要修改RecyclerView的源碼,在ViewHolder的構(gòu)造函數(shù)這里直接添加onclicklistener只能對(duì)整個(gè)item設(shè)置click事件,不能對(duì)item里面的子布局設(shè)置click響應(yīng)事件。我不推薦這種做法,破壞了RecyclerView的封裝性。只是在這里提一下,多提供一種思路。

  • 不修改源碼,在適配器設(shè)置OnItemClickListener
    不多說,上代碼。
 //ReclcyerView 的適配器
    class HomeAdapter extends  RecyclerView.Adapter<HomeAdapter.MyHomeViewHolder> {
        private List<String> mData;
        public HomeAdapter(List<String> data) {
            super();
            mData = data;
        }

        @Override
        public MyHomeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            //加載布局
            MyHomeViewHolder  viewHolder = new MyHomeViewHolder(LayoutInflater.from(MainActivity.this).inflate(R.layout.view_item, parent, false));
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(final MyHomeViewHolder holder, final int position) {
            //onBindViewHolder 初始化布局
            holder.mNum.setText(mData.get(position));
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, "onclick" + position, Toast.LENGTH_LONG).show();
                    addData(holder.getLayoutPosition());
                }
            });

            holder.itemView.setOnLongClickListener(new View.OnLongClickListener(){

                @Override
                public boolean onLongClick(View v) {
                    Toast.makeText(MainActivity.this, "on long click" + position, Toast.LENGTH_LONG).show();
                    removeData(holder.getLayoutPosition());
                    return true;
                }
            });

        }

        @Override
        public int getItemCount() {
            return mData.size();
        }

        class MyHomeViewHolder extends RecyclerView.ViewHolder {
           TextView mNum;
            public MyHomeViewHolder(View itemView) {
                super(itemView);
                //ViewHolder查找布局
                mNum = (TextView) itemView.findViewById(R.id.txt_num);
            }
        }
    }

如上代碼,在onBindViewHolder方法中,我們通過viewholder獲取到item中的布局,對(duì)item中的設(shè)置響應(yīng)的點(diǎn)擊事件。相對(duì)于修改源碼的來說,這個(gè)可以對(duì)item中的view的點(diǎn)擊事件進(jìn)行設(shè)置。注意父布局搶占子布局焦點(diǎn)的問題。記得設(shè)置mRecyclerView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS)這樣父布局焦點(diǎn)在子布局獲取焦點(diǎn)之后。
同樣的你也可以給Apdater添加OnItemListener回調(diào)接口以及成員變量,通過構(gòu)造函數(shù)或者set方法設(shè)置回調(diào),這樣就可以將onClick的處理從adapter里面抽離出去。

  • 實(shí)現(xiàn) RecyclerView.OnItemTouchListener
    實(shí)現(xiàn)RecyclerView.OnItemTouchListener監(jiān)聽RecyclerView的touch事件,通過捕獲touch事件,根據(jù)event的x,y以及RecyclerView的findChildViewUnder(e.getX(),e.getY())來獲取到當(dāng)前被觸摸的view。然后利用手勢(shì)來判斷是長按還是點(diǎn)擊,從而回調(diào)相應(yīng)的回調(diào)函數(shù)。
    示例代碼如下:
public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {

    private GestureDetector mGestureDetector;
    private OnItemClickListener mListener;


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

        void onItemLongClick(View view, int position);
    }

    public RecyclerViewClickListener(Context context, final RecyclerView recyclerView,OnItemClickListener listener){
        mListener = listener;
        mGestureDetector = new GestureDetector(context,
                new GestureDetector.SimpleOnGestureListener(){ 
                    //click
                    @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;
                    }
                    //long click
                    @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) {
     
        if(mGestureDetector.onTouchEvent(e)){
            return true;
        }else
            return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
      Log.i("doris", "onTouchEvent");
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         Log.i("doris", "onRequestDisallowInterceptTouchEvent");
    }
}

其實(shí)不通過手勢(shì)來判斷也是可以的,那就是監(jiān)聽KEY_DOWN以及KEY_MOVE,KEY_UP,根據(jù)動(dòng)作間隔以及移動(dòng)的具體來判斷是點(diǎn)擊還是滑動(dòng)還是長按。實(shí)現(xiàn)方法可參考揭開RecyclerView的神秘面紗(二):處理RecyclerView的點(diǎn)擊事件.不過與其自己計(jì)算不如用google已經(jīng)封裝好的,這樣可以更精確的判斷點(diǎn)擊以及長按事件,避免了由于頻繁快速的操作導(dǎo)致計(jì)算錯(cuò)誤的情況。

以為這就完了? 不不不,還有辦法呢,而且我個(gè)人比較喜歡這種方法。因?yàn)楹唵危肭中圆淮?,不用修改源碼,只需設(shè)置回調(diào)接口,就可以方便的使用了。準(zhǔn)備好了嗎? 我要放代碼啦!?。?!

  • 重寫RecyclerView的onChildAttachedToWindow方法
    首先讓我們一起來看看onChildAttachedToWindow方法:
  /**
     * Called when an item view is attached to this RecyclerView.
     *
     * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
     * of child views as they become attached. This will be called before a
     * {@link LayoutManager} measures or lays out the view and is a good time to perform these
     * changes.</p>
     *
     * @param child Child view that is now attached to this RecyclerView and its associated window
     */
    public void onChildAttachedToWindow(View child) {
    }

以我蹩腳的英語水平看了一下英文注釋,這個(gè)方法就是在itemView要被attach(關(guān)聯(lián))到RecylclerView的時(shí)候調(diào)用,RecyclerView的子布局可以重寫這個(gè)方法,從而在View要被``attach的時(shí)候?qū)?/code>itemView做一些操作。這個(gè)方法LayoutManager`繪制view之前調(diào)用,所以如果你想對(duì)view做特殊的處理,這個(gè)方法是一個(gè)很好的切入口。果然GOOGLE的開發(fā)人員還是很有愛的,一早就給我們預(yù)留了方便我們拓展的方法,機(jī)智如你啦。
好吧,看完了上面的這個(gè)方法的解說,你的大腦可以快速運(yùn)轉(zhuǎn)了,這說明了什么?我可以利用這個(gè)干什么? 好吧,謎底揭曉,既然可以在這里操作到每一個(gè)view,那我們就可以在這里對(duì)view進(jìn)行事件監(jiān)聽的設(shè)置啦,不是嗎不是嗎? 對(duì)對(duì)對(duì),沒錯(cuò)。不過呢,這個(gè)方法也只能對(duì)整個(gè)item的view進(jìn)行設(shè)置,所以如果你的item沒有多個(gè)button的話,其實(shí)用這個(gè)方法是很不錯(cuò)的。話不多說,又到了貼代碼的時(shí)候了。有沒有那么一丟丟的小期待,haa!

//增加一個(gè)私有的ItemListener
private interface ItemListener extends OnClickListener, OnFocusChangeListener, OnKeyListener {
  }
  //在構(gòu)造函數(shù)里創(chuàng)建該對(duì)象,并重寫方法如下
   mItemListener = new ItemListener() {
          @Override
          public boolean onKey(View v, int keyCode, KeyEvent event) {
              if(null != mOnItemOnKeyListener){
                  mOnItemOnKeyListener.onKey(v,keyCode,event);
              }
              return false;
          }

          /**
           * 子控件的點(diǎn)擊事件
           * @param itemView
           */
          @Override
          public void onClick(View itemView) {
              if (null != mOnItemClickListener) {
                  mOnItemClickListener.onItemClick(RecyclerViewTV.this, itemView, getChildLayoutPosition(itemView));
              }
          }

          /**
           * 子控件的焦點(diǎn)變動(dòng)事件
           * @param itemView
           * @param hasFocus
           */
          @Override
          public void onFocusChange(View itemView, boolean hasFocus) {
              if (null != mOnItemListener) {
                  if (null != itemView) {
                      mItemView = itemView; // 選中的item.
                      itemView.setSelected(hasFocus);
                      if (hasFocus) {
                          mCurrentSelectView = mItemView;
                          mOnItemListener.onItemSelected(RecyclerViewTV.this, itemView, getChildLayoutPosition(itemView));
                      } else {
                          mOnItemListener.onItemPreSelected(RecyclerViewTV.this, itemView, getChildLayoutPosition(itemView));
                      }
                  }
              }
          }
      };
       

onChildAttachedToWindow的重寫方法如下:

  @Override
  public void onChildAttachedToWindow(View child) {
      
      if (!child.hasOnClickListeners()) {
          child.setOnClickListener(mItemListener);
      }
     
      if (child.getOnFocusChangeListener() == null) {
          child.setOnFocusChangeListener(mItemListener);
      }

      child.setOnKeyListener(mItemListener);
  }

這個(gè)代碼是引用自androidtvwidget大家可以在github里面搜索,里面有recyclerView的封裝,用起來還是不錯(cuò)的,而且是Android TV,當(dāng)然如果你不是Android TV的開發(fā)者,一樣也可以修改一下用于手機(jī)端的。

好了,到這里我們的RecyclerView如何設(shè)置OnItmeClick事件的方法匯總就完了。如果還有發(fā)現(xiàn)其他更好的辦法,我會(huì)更新進(jìn)來。
喜歡我的匯總的可以點(diǎn)個(gè)贊,你們的每一個(gè)點(diǎn)贊都是對(duì)我學(xué)習(xí)路上的莫大的鼓勵(lì),當(dāng)然歡迎大家留言交流,共同進(jìn)步。

最后貼出參考鏈接,這幾篇都是我覺得還不錯(cuò)的資源:

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

https://stackoverflow.com/questions/24885223/why-doesnt-recyclerview-have-onitemclicklistener

https://stackoverflow.com/questions/24471109/recyclerview-onclick/26196831#26196831

Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,881評(píng)論 25 709
  • 特別聲明: 本文轉(zhuǎn)發(fā)自:【江清清的博客】http://blog.csdn.net/developer_jiangq...
    _猜火車_閱讀 37,831評(píng)論 11 70
  • 這篇文章分三個(gè)部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,688評(píng)論 0 27
  • 人找人難,話找話更難。 人這一生,都在尋找那個(gè)能過心的人。 人群中,我們滔滔不絕,我們歡聲笑語,我們有很多朋友,看...
    花雅雅閱讀 621評(píng)論 1 5
  • 文/阿墨姑娘 天氣有點(diǎn)寒冷,夜色不太溫柔。 僅有幾顆寂寥的星星百無聊賴地掛在天邊。 “真不幸?!贝┧笤诤L(fēng)與星辰里...
    阿墨姑娘閱讀 495評(píng)論 19 8

友情鏈接更多精彩內(nèi)容