RecycleView 点击事件

来源:互联网 发布:淘宝店铺软文 编辑:程序博客网 时间:2024/05/29 17:38

RecycleView 点击事件


       此文章讨论RecycleView点击事件的设置,参考网上两钟主流的设置方法并且对比两张方法的使用场景.

       一开始我接触的就是通过Adapter设置RecycleView的点击事件监听函数.但是发现其实这样相当于把点击事件从RecycleView里面分离出来.然后在后期发现可以在onTouchEvent函数里面拦截触摸事件然后判断是否触发点击事件.在一些场景下这两钟情况都能很好的把问题处理好.直到现在我的布局文件里面还包含更多的控件的时候,发现第二种办法的弊端.然后把这两种实现方法的原理细看一遍,发现其实他们实现的原理是赤裸裸的不一样.

通过Adapter设置点击事件


       通过Adapter设置点击事件,很好理解.本质就是给View绑定事件监听函数.所以可以看到RecycleView是在Adapter#onCreateViewHolder函数里面构造出我们需要的View.当然在这个时候,我们可以在Adapter#onCreateViewHolder或者Adapter#onBindViewHolder函数里面设置点击事件监听函数即可.

  • 在onCreateViewHolder函数里面设置点击事件监听函数
public RvAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        View view;        final RvAdapter.ViewHolder holder;        view = mLayoutInflater.inflate(R.layout.item_simple_textview, parent, false);        holder = new ViewHolder(view);        view.findViewById(R.id.txtTag).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Log.i(TAG, mList.get(holder.getLayoutPosition()) + " is click");            }        });        return holder;    }

       在构造View的同时给View绑定点击事件监听函数,并在监听函数里面持有holder的引用.当回调监听函数的时候通过ViewHolder#getLayoutPosition函数获取position.

  • 在onBindViewHolder函数里面设置设置点击事件监听函数
public void onBindViewHolder(final RvAdapter.ViewHolder holder, final int position) {        holder.txtTag.setText(mList.get(position));        holder.itemView.findViewById(R.id.txtTag).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Log.i(TAG, mList.get(position) + " is click");            }        });    }

       在绑定事件里面给View绑定点击事件监听函数.

两种办法的区别是也是很明显的.对于第一种方法,因为onCreateViewHolder调用的次数有限的.只当需要创建ViewHolder的时候才会调用,所以我们知道它只需对每一个ViewHolder内含的View创建相应的点击事件监听函数就可以了.但是对于第二种方法,就是每次绑定数据的时候都会创建点击事件监听函数.当然喜欢怎么使用是大家的自由.

       总结:通过Adapter设置点击事件,本质上是对View直接设置点击事件监听函数.

通过在onTouchEvent函数拦截触摸事件并且派发点击事件


       首先我们看看RecycleView的部分源码判断
这里写图片描述

       在RecycleView的onTouchEvent函数里面,首先会调用dispatchOnItemTouch函数进行一些工作.当然我们顺藤摸瓜进去函数里面看看到底里面做了些什么操作?

这里写图片描述

其实这里就是调用我们通过addOnItemTouchListener函数添加的OnItemTouchListener对象.明白了把?在RecycleView分发点击事情之前会调用我们添加的OnItemTouchListener对象.然而如果我们在OnItemTouchListener对象里面进行点击事件的判断不就实现了点击事件的处理?事实上,第二种设置点击事件的方法原理归根到底就是这样,通过对onTouchEvent的拦截判断是否回调点击事件函数.从而显示点击事件回调函数.

       接下来看看种方法代码是怎么实现的

       首先继承RecycleView实现一个自定义控件

public class InterceptorRecycleView extends RecyclerView {    OnItemClickListener mOnItemClickListener;    public InterceptorRecycleView(Context context) {        this(context,null);    }    public InterceptorRecycleView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs,0);    }    public InterceptorRecycleView(Context context, @Nullable AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }...}

       在内部添加设置点击事件回调函方法

public void setOnItemClickListener(OnItemClickListener listener) {        if (listener == mOnItemClickListener)            return;        mOnItemClickListener = listener;    }

       继承RecyclerView.SimpleOnItemTouchListener,在ItemClickInterceptor函数判断什么时候调用点击事件监听函数.

    class ItemClickInterceptor extends RecyclerView.SimpleOnItemTouchListener {        Context mContext;        GestureDetector mGestureDetector;        ItemClickInterceptor(Context context) {            mContext = context;            mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {                @Override                public boolean onSingleTapUp(MotionEvent e) {                    return true;                }                @Override                public void onLongPress(MotionEvent e) {                    View view;                    int position;                    view = findChildViewUnder(e.getX(), e.getY());                    if (null != view && null != mOnItemClickListener) {                        position = getChildLayoutPosition(view);                        mOnItemClickListener.onLongClick(InterceptorRecycleView.this, view, position);                    }                }            });        }        @Override        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {            View view;            int position;            view = findChildViewUnder(e.getX(), e.getY());            if (null != view && mGestureDetector.onTouchEvent(e) && null != mOnItemClickListener) {                position = getChildLayoutPosition(view);                mOnItemClickListener.onClick(InterceptorRecycleView.this, view, position);                return true;            }            return false;        }    }

其实我们是通过GestureDetector类实现点击事件的监听

在GestureDetector类的onLongPress方法调用长按事件回调函数.因为GestureDetector类已经能帮我们判断什么时候是长按.

但是GestureDetector类并没有实现什么时候是点击.但是当没发生滑动的时候,当事件派发完毕最后是调用onSingleTapUp函数.代表手指已经抬起.我们可以在onSingleTapUp函数里面调用点击事件回调函数.

但是我的做法并不是这样.我选择onSingleTapUp返回ture,并且在onInterceptTouchEvent里面调用点击事件回调函数.(其实两种效果是一样的)

最后添加mOnItemTouchListeners链表里面

   public InterceptorRecycleView(Context context, @Nullable AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        addOnItemTouchListener(new ItemClickInterceptor(context));    }
注意,判断点击是否在RecycleView的某一个控件上是通过findChildViewUnder(e.getX(), e.getY())方法完成.仔细看findChildViewUnder函数只能判断点位点击落到RecycleView的哪一个item并没办法判断是落在item内部的哪一个控件.

有两个致命的缺点

  • 没办法拿到和点击的View绑定的数据

  • 没办法定位点击了哪一个内部控件,只能定位到点击了RecycleView的哪一个item


此外这样的方式也有一个问题,我只是想设置点击事件回调函数而已.居然要动用到自定义控件?太小题大做了!

       针对上面的两个致命缺点,也给出思路.

  • 把ItemClickInterceptor内部类移到Adapter里面,ItemClickInterceptor作为Adapter的内部类就可以访问mList,这个时候我拿到position还担心拿不到和View绑定的数据?但是这样一来还不是把点击事件回调函数放回了Adapter里面?
  • 通过阅读findChildViewUnder函数,发现通过x,坐标只能定位到RecycleView的item这个问题.没办法从根本上解决,如果有需求需要给内部的控件添加点击事件回调函数.对不起这种办法没办法实现.只能采用第一种方法.

总结


       综合上述,作为一个精明的开发者你应该心里早就有自己的见解了把!
然而还是需要来个总结:

  • 第一种办法能够很精确的定位到内部控件,并且可以针对控件设置相应的点击事件回调函数.只是代码有点多.
  • 第二章办法本质上只能定位到RecycleView的item.如果给item内部的控件添加点击事件回调函数那是不可能实现的.如果只需对item设置点击事件可以考虑这种方法.
0 0
原创粉丝点击