美文网首页
RecyclerView无法添加onItemClickListe

RecyclerView无法添加onItemClickListe

作者: crush_d872 | 来源:发表于2018-12-20 11:51 被阅读0次

自从RecyclerView发布以来,由于其高度的可交互性被广泛使用。但是RecyclerView确没有像ListView一样提供onItemClickListener却让人比较难过,网上搜索了一番有不少解决方案,但是其本质都是通过给每个item添加onClickListener来模仿一个伪onItemClickListener,这种为每个item添加点击监听的解决方案不用多想也知道是浪费性能的方法。能不能像ListView那样使用一个监听解决问题呢?

Recyclerview虽然没有提供onItemClickListener方法,但是提供了addOnItemTouchListener方法:

RecyclerView.addOnItemTouchListener(OnItemTouchListener listener)

既然可以添加触摸监听,那么我们完全可以获取触摸手势来识别点击事件,然后通过触摸坐标来判断点击的是哪一个item,虽然听起来比较复杂,但是sdk 的 api已经为我们实现了大部分方法,我们只需要实现接口几行代码就可以搞定了。

下面先说一下使用方法,后面详细介绍其实现原理:

如何使用

rlView.addOnItemTouchListener(new OnRecyclerItemClickListener(rlView) {
        @Override
        public void onItemClick(RecyclerView.ViewHolder vh,int positon) {
            
        }

        @Override
        public void onItemLongClick(RecyclerView.ViewHolder vh) {

        }
    });

其中OnRecyclerItemClickListener是自定义的一个触摸监听器,代码如下:
仅有短短十多行代码,sdk已经为我们实现了大部分功能

public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {

private GestureDetectorCompat mGestureDetector;
private RecyclerView rlView;

public OnRecyclerItemClickListener(RecyclerView rlView){
    this.rlView = rlView;
    mGestureDetector = new GestureDetectorCompat(rlView.getContext(),new ItemTouchHelperGestureListener());
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
    mGestureDetector.onTouchEvent(e);
    return false;
}

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

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

}

private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onSingleTapUp(MotionEvent e){
        View child = rlView.findChildViewUnder(e.getX(),e.getY());
        if (child != null){
            RecyclerView.ViewHolder vh = rlView.getChildViewHolder(child);
            int position = rlView.getChildLayoutPosition(child);
            onItemClick(vh,position);
        }
        return true;
    }
}
public abstract void onItemClick(RecyclerView.ViewHolder vh,int position);
public abstract void onItemLongClick(RecyclerView.ViewHolder vh);

}

实现原理

RecyclerView提供了设置触摸监听的方法,那么我们定义一个类OnRecyclerItemClickListener实现OnItemTouchListener,我们需要实现其3个方法:

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
}

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

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}

其中第三个方法是处理触摸事件冲突的,跟我们没关系不用管它,前两个方法是不是很熟悉呢,这不就是View的事件分发机制里面的事件拦截和事件处理的两个方法吗,参数里为我们提供了触摸事件的数据MotionEvent,我们要做的就是去解析坐标点和触摸规律来识别触摸手势,然后获取触摸的是哪一个item,再执行我们的回调,听起来很复杂,但是sdk已经为我们实现了手势的识别,所以我们不需要再做过多操作:

GestureDetectorCompat 就是处理手势的类:手势探测器,它比GestureDetector能更好兼容低版本的api,但使用方法是一致的,我们实例化一个手势探测器:

mGestureDetector = new GestureDetectorCompat(context,new GestureListener(){...});

我们实例化手势探测器的时候需要提供一个手势监听器:OnGestureListener,探测器识别出手势后就会回调手势监听器中对应的方法,我们就可以在回调方法中做我们想做的事情了。

sdk为我们提供了两个手势监听器:OnGestureListenerOnDoubleTapListener

OnGestureListener的回调接口如下:

//用户按下屏幕就会触发
public boolean onDown(MotionEvent e);
//如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行
public void onShowPress(MotionEvent e);
//一次单独的轻击抬起操作,也就是轻击一下屏幕,就是普通点击事件
public boolean onSingleTapUp(MotionEvent e);
//在屏幕上拖动事件
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
//长按触摸屏,超过一定时长,就会触发这个事件
public void onLongPress(MotionEvent e);
//滑屏,用户按下触摸屏、快速移动后松开
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);

OnDoubleTapListener的回调接口如下:

//单击事件。用来判定该次点击是SingleTap而不是DoubleTap,
//如果连续点击两次就是DoubleTap手势,如果只点击一次,
//系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,
//然后触发SingleTapConfirmed事件
public boolean onSingleTapConfirmed(MotionEvent e);
//双击事件
public boolean onDoubleTap(MotionEvent e);
//双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作
public boolean onDoubleTapEvent(MotionEvent e);

可以看出OnGestureListener主要回调各种单击事件,而OnDoubleTapListener回调各种双击事件。而我们需要处理的点击事件其实就是上面的:onSingleTapUp()

此外sdk 还提供了一个外部类SimpleOnGestureListener,这个类实现了上面两个接口的所有方法,但全都是空实现,函数体里什么也没写,其中就是把上面两个接口合并一下,给出默认的空实现,这样继承SimpleOnGestureListener的时候就不用实现每一个方法了,既然如此,那么我们定义一个类去继承它吧。

定义一个ItemTouchHelperGestureListener 继承自SimpleOnGestureListener ,实现onSingleTapUp方法:

private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
  @Override
  public boolean onSingleTapUp(MotionEvent e) {
  }

}

到这里,已经获取到了RecyclerView的点击事件和触摸事件数据MotionEvent ,那么我们怎么知道点击的是哪一个item呢?RecyclerView已经为我们提供了这样的方法:findChildViewUnder(),我们可以通过这个方法获得点击的item,同时我们调用RecyclerView的另一个方法getChildViewHolder(),可以获得该item的ViewHolder,还提供了一个方法getChildLayoutPosition(),可以获得当前item的position,最后再回调我们定义的虚方法onItemClick()就ok了,这样我们就可以在外部实现该方法来获得item的点击事件了:

private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onSingleTapUp(MotionEvent e){
        View child = rlView.findChildViewUnder(e.getX(),e.getY());
        if (child != null){
            RecyclerView.ViewHolder vh = rlView.getChildViewHolder(child);
            int position = rlView.getChildLayoutPosition(child);
            onItemClick(vh,position);
        }
        return true;
    }
}

以上的解决办法无法精确到item里的子控件的点击。下面这个方法可以解决子控件点击问题。

在adaper中定义接口并提供回调

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

}

public void setOnItemClickListener(OnItemClickListener mOnItemClickListener)

{
this.mOnItemClickListener = mOnItemClickListener;
}
接下来对item中的控件进行点击事件监听并回调给我们自定义的监听:

@Override
public void onBindViewHolder(final MyViewHolder holder, final int position)
{
    holder.tv.setText(mList.get(position));
    if (mOnItemClickListener != null)
    {
        holder.tv.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                int pos = holder.getLayoutPosition();
                mOnItemClickListener.onItemClick(holder.tv,pos);
            }
        });
        holder.tv.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                int pos = holder.getLayoutPosition();
                mOnItemClickListener.onItemLongClick(holder.tv,pos);
                return false;
            }
        });
    }
}

在Activity进行监听

mHomeAdaper.setOnItemClickListener(new HomeAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(View view, int position) {
            Toast.makeText(RecyclerViewActivity.this,"点击第"+(position+1)+"条",Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onItemLongClick(View view, final int position) {
            new AlertDialog.Builder(RecyclerViewActivity.this)
                    .setTitle("确认删除嘛")
                    .setNegativeButton("取消",null)
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            mHomeAdaper.removeData(position);
                        }
                    })
                    .show();
        }
    });

长按时会弹出对话框,删除时会有消失的动画:

相关文章

网友评论

      本文标题:RecyclerView无法添加onItemClickListe

      本文链接:https://www.haomeiwen.com/subject/fbqykqtx.html