android技术专栏recyclerViewAndroid开发

RecyclerView --onItemClick设置汇总

2017-09-21  本文已影响277人  DorisSunny

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

  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的封装性。只是在这里提一下,多提供一种思路。

 //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里面抽离出去。

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已经封装好的,这样可以更精确的判断点击以及长按事件,避免了由于频繁快速的操作导致计算错误的情况。

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

  /**
     * 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 使用完全解析 体验艺术般的控件

上一篇下一篇

猜你喜欢

热点阅读