RecyclerView --onItemClick设置汇总

众所周知,RecyclerView是继承自ViewGroup的,而不是像ListView一样继承自AbsListview.所以RecyclerView没有OnItemClickListener,没有OnItemLongClickListener,更没有OnItemSelectedListener.所以这都要我们自己实现。What!!! Are you kidding me? 严肃脸,没错,就是要我们自己来实现。实现的方法有很多种,我这里做了一下总结,接下来我们一一列举出来并且对比一下,当然最后选哪一个还是看你自己喜欢咯。

  • 修改RecyclerView的源码,在ViewHolder里面添加监听。
  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里面的构造方法里面添加如下代码。

  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),根据v来获取v的位置。 mOnItemClickListener.onItemClick(v, getChildLayoutPosition(v))
                }
            }); //添加这一句就可以添加onclick事件了。
        }  
  

这个方法虽然可行,但是需要修改RecyclerView的源码,在ViewHolder的构造函数这里直接添加onclicklistener只能对整个item设置click事件,不能对item里面的子布局设置click响应事件。我不推荐这种做法,破坏了RecyclerView的封装性。只是在这里提一下,多提供一种思路。

  • 不修改源码,在适配器设置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中的布局,对item中的设置响应的点击事件。相对于修改源码的来说,这个可以对item中的view的点击事件进行设置。注意父布局抢占子布局焦点的问题。记得设置mRecyclerView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS)这样父布局焦点在子布局获取焦点之后。
同样的你也可以给Apdater添加OnItemListener回调接口以及成员变量,通过构造函数或者set方法设置回调,这样就可以将onClick的处理从adapter里面抽离出去。

  • 实现 RecyclerView.OnItemTouchListener
    实现RecyclerView.OnItemTouchListener监听RecyclerView的touch事件,通过捕获touch事件,根据event的x,y以及RecyclerView的findChildViewUnder(e.getX(),e.getY())来获取到当前被触摸的view。然后利用手势来判断是长按还是点击,从而回调相应的回调函数。
    示例代码如下:
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");
    }
}

其实不通过手势来判断也是可以的,那就是监听KEY_DOWN以及KEY_MOVE,KEY_UP,根据动作间隔以及移动的具体来判断是点击还是滑动还是长按。实现方法可参考揭开RecyclerView的神秘面纱(二):处理RecyclerView的点击事件.不过与其自己计算不如用google已经封装好的,这样可以更精确的判断点击以及长按事件,避免了由于频繁快速的操作导致计算错误的情况。

以为这就完了? 不不不,还有办法呢,而且我个人比较喜欢这种方法。因为简单,入侵性不大,不用修改源码,只需设置回调接口,就可以方便的使用了。准备好了吗? 我要放代码啦!!!!

  • 重写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) {
    }

以我蹩脚的英语水平看了一下英文注释,这个方法就是在itemView要被attach(关联)到RecylclerView的时候调用,RecyclerView的子布局可以重写这个方法,从而在View要被``attach的时候对itemView做一些操作。这个方法LayoutManager`绘制view之前调用,所以如果你想对view做特殊的处理,这个方法是一个很好的切入口。果然GOOGLE的开发人员还是很有爱的,一早就给我们预留了方便我们拓展的方法,机智如你啦。
好吧,看完了上面的这个方法的解说,你的大脑可以快速运转了,这说明了什么?我可以利用这个干什么? 好吧,谜底揭晓,既然可以在这里操作到每一个view,那我们就可以在这里对view进行事件监听的设置啦,不是吗不是吗? 对对对,没错。不过呢,这个方法也只能对整个item的view进行设置,所以如果你的item没有多个button的话,其实用这个方法是很不错的。话不多说,又到了贴代码的时候了。有没有那么一丢丢的小期待,haa!

//增加一个私有的ItemListener
private interface ItemListener extends OnClickListener, OnFocusChangeListener, OnKeyListener {
  }
  //在构造函数里创建该对象,并重写方法如下
   mItemListener = new ItemListener() {
          @Override
          public boolean onKey(View v, int keyCode, KeyEvent event) {
              if(null != mOnItemOnKeyListener){
                  mOnItemOnKeyListener.onKey(v,keyCode,event);
              }
              return false;
          }

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

          /**
           * 子控件的焦点变动事件
           * @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);
  }

这个代码是引用自androidtvwidget大家可以在github里面搜索,里面有recyclerView的封装,用起来还是不错的,而且是Android TV,当然如果你不是Android TV的开发者,一样也可以修改一下用于手机端的。

好了,到这里我们的RecyclerView如何设置OnItmeClick事件的方法汇总就完了。如果还有发现其他更好的办法,我会更新进来。
喜欢我的汇总的可以点个赞,你们的每一个点赞都是对我学习路上的莫大的鼓励,当然欢迎大家留言交流,共同进步。

最后贴出参考链接,这几篇都是我觉得还不错的资源:

揭开RecyclerView的神秘面纱(二):处理RecyclerView的点击事件

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

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

Android RecyclerView 使用完全解析 体验艺术般的控件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容