RecyclerView --onItemClick设置汇总
众所周知,RecyclerView
是继承自ViewGroup
的,而不是像ListView
一样继承自AbsListview
.所以RecyclerView没有OnItemClickListener
,没有OnItemLongClickListener
,更没有OnItemSelectedListener
.所以这都要我们自己实现。What!!! Are you kidding me? 严肃脸,没错,就是要我们自己来实现。实现的方法有很多种,我这里做了一下总结,接下来我们一一列举出来并且对比一下,当然最后选哪一个还是看你自己喜欢咯。
- 修改RecyclerView的源码,在ViewHolder里面添加监听。
- 首先在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