您的位置:首页 > 移动开发 > Android开发

为RecyclerView的Item添加点击及长按事件的三种方法

2017-06-14 23:07 543 查看

简介

我们知道RecyclerView作为一个优秀的ListView的替代品现在已经凭借其更为方便的使用,更多样的功能,更炫酷的界面设计等等广为使用,但是官方并没有提供类似ListView的OnItemClickListener的接口。因此如何实现类似于ListView的item点击事件到底该如何实现呢,下面我们通过一个简单的例子来进行介绍。

简单的RecyclerView的建立

首先我们先建立一个最最基本的RecyclerView,下面是我们的主布局activity_main.xml,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.luckybear.myrecyclerview.MainActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_my_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>


我们在主布局中仅放置一个RecyclerView,下面来看看我们的activity实现:

public class MainActivity extends AppCompatActivity {

private RecyclerView mMyRecyclerView;
private MyRecyclerAdapter mMyRecyclerAdapter;
private ArrayList<String> mData;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mMyRecyclerView = (RecyclerView) findViewById(R.id.rv_my_recycler);
mMyRecyclerAdapter = new MyRecyclerAdapter(this, getData());
mMyRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mMyRecyclerView.setAdapter(mMyRecyclerAdapter);

}

public ArrayList<String> getData() {
mData = new ArrayList<>();
for (int i = 0; i < 50; i ++) {
mData.add("item " + i);
}
return mData;
}
}


如上所示,首先建立RecyclerView,之后我们为其创建一个Adapter,我们的数据源即为mData,接下来我们来看看adapter的实现:

public class MyRecyclerAdapter extends RecyclerView.Adapter {

ArrayList<String> mData;
Context mContext;
TextView mMyTextView;

public MyRecyclerAdapter(Context context, ArrayList<String> data) {
mContext = context;
mData = data;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext).infl
4000
ate(R.layout.recycler_item, parent, false);
MyViewHolder viewHolder = new MyViewHolder(itemView);
return viewHolder;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
mMyTextView = (TextView) holder.itemView.findViewById(R.id.tv_my_item);
mMyTextView.setText(mData.get(position));
}

@Override
public int getItemCount() {
return mData.size();
}
}


简单到不能呼吸,最简单的adapter,有一个构造函数,在刚才的activity中创建adapter时进行调用,数据源即在这里传入。然后在onCreateViewHolder()方法中创建得到我们的itemView,并且将其与viewHolder绑定,在onBindViewHolder()方法中将数据设置到我们的itemView的textView中,我们的每一个item的布局定义在recycler_item.xml中,定义如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/tv_my_item"
android:layout_width="match_parent"
android:layout_height="72dp"
android:gravity="center_vertical"/>

<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/black"/>

</LinearLayout>


如上所示,仅有一个textView及一个分割线。最后我们的ViewHolder也是最基本的ViewHolder,定义如下:

public class MyViewHolder extends RecyclerView.ViewHolder {

public MyViewHolder(View itemView) {
super(itemView);
}
}


这样我们就创建了一个最简单的RecyclerView,实现效果如下所示:



这个RecyclerView是最为基本的RecyclerView了,它支持最最基本的功能,但是当你点击它的每一项时,它并没有任何响应,接下来我们为其添加最基本的响应。

方法一:在Adapter中为Item设置点击监听

既然官方未提供相应的实现那我们也只有自己来实现它了,接下来我们来说明如何实现这个点击事件,如果我们想显示我们目前点击的每个item所填充的数据,这个其实在adapter内部就可以轻易的实现,adapter修改如下:

public class MyRecyclerAdapter extends RecyclerView.Adapter implements View.OnClickListener, View.OnLongClickListener {

ArrayList<String> mData;
Context mContext;
TextView mMyTextView;

public MyRecyclerAdapter(Context context, ArrayList<String> data) {
mContext = context;
mData = data;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.recycler_item, parent, false);
MyViewHolder viewHolder = new MyViewHolder(itemView);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
return viewHolder;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
mMyTextView = (TextView) holder.itemView.findViewById(R.id.tv_my_item);
mMyTextView.setText(mData.get(position));
holder.itemView.setTag(position);
}

@Override
public int getItemCount() {
return mData.size();
}

@Override
public void onClick(View v) {
Toast.makeText(mContext, "Click " + mData.get((int)v.getTag()), Toast.LENGTH_SHORT).show();
}

@Override
public boolean onLongClick(View v) {
Toast.makeText(mContext, "LongClick " + mData.get((int)v.getTag()), Toast.LENGTH_SHORT).show();
return true;
}
}


如上所示,

implements View.OnClickListener接口

在函数在OnClick()方法中得到数据源的数据,并将其显示在Toast中

在onCreateViewHolder()方法中或者onBindViewHolder()方法中为每个itemView设置OnClickListener

在onBindViewHolder()时将相应的位置信息设置为view的Tag,在onClick()方法中进行使用

可以看到,我们已经得到了用户的点击事件,并且正确的处理了这个点击,但是有很多时候我们并不能仅仅在adapter中来处理这个点击事件,我们很多业务逻辑需要在activity中或者fragment中进行处理以满足交互或者调整UI显示,我们在原有的基础上进行修改,让这个Toast在acticvity中来显示,我们一定已经想到了吧,那就是回调,我们回调三部曲:

在adapter中定义interface

在activity中实现这个回调接口并将回调对象设置到adapter中

在adapter中的合适位置进行调用

我们的adapter修改如下:

public class MyRecyclerAdapter extends RecyclerView.Adapter implements View.OnClickListener, View.OnLongClickListener {

ArrayList<String> mData;
Context mContext;
TextView mMyTextView;
OnItemClickListener mOnItemClickListener;
OnItemLongClickListener mOnItemLongClickListener;

public MyRecyclerAdapter(Context context, ArrayList<String> data) {
mContext = context;
mData = data;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.recycler_item, parent, false);
MyViewHolder viewHolder = new MyViewHolder(itemView);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
return viewHolder;
}

public interface OnItemClickListener {
void OnItemClick(View v);
}

public interface OnItemLongClickListener {
void OnItemLongClick(View v);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
mMyTextView = (TextView) holder.itemView.findViewById(R.id.tv_my_item);
mMyTextView.setText(mData.get(position));
holder.itemView.setTag(position);
}

@Override
public int getItemCount() {
return mData.size();
}

@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
mOnItemClickListener.OnItemClick(v);
}
}

@Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
mOnItemLongClickListener.OnItemLongClick(v);
}
return true;
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}

public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
mOnItemLongClickListener = onItemLongClickListener;
}
}


如上所示,定义一个回调接口,并在OnClick()中进行调用,这个接口在activity中实现,具体实现如下:

public class MainActivity extends AppCompatActivity implements MyRecyclerAdapter.OnItemClickListener,
MyRecyclerAdapter.OnItemLongClickListener{

private RecyclerView mMyRecyclerView;
private MyRecyclerAdapter mMyRecyclerAdapter;
private ArrayList<String> mData;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mMyRecyclerView = (RecyclerView) findViewById(R.id.rv_my_recycler);
mMyRecyclerAdapter = new MyRecyclerAdapter(this, getData());
mMyRecyclerAdapter.setOnItemClickListener(this);
mMyRecyclerAdapter.setOnItemLongClickListener(this);
mMyRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mMyRecyclerView.setAdapter(mMyRecyclerAdapter);

}

public ArrayList<String> getData() {
mData = new ArrayList<>();
for (int i = 0; i < 50; i ++) {
mData.add("item " + i);
}
return mData;
}

@Override
public void OnItemClick(View v) {
Toast.makeText(MainActivity.this, "Click " + mData.get((int)v.getTag()), Toast.LENGTH_SHORT).show();
}

@Override
public void OnItemLongClick(View v) {
Toast.makeText(MainActivity.this, "LongClick " + mData.get((int)v.getTag()), Toast.LENGTH_SHORT).show();
}
}


如上为activity中的接口的实现并将此接口设置到adapter对象中。

实现效果如下:

点击事件响应

长按事件响应



方法二: 利用RecyclerView的addOnItemTouchListener方法

此方法利用了RecyclerView的addOnItemTouchListener方法,对事件进行相应的拦截处理之后在点击及长按事件的处理中会调在addOnItemTouchListener定义的点击及长按接口方法进行相应的处理。我们需要添加一个RecyclerView的事件处理类如下:

public class RecyclerViewTouchListener 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 RecyclerViewTouchListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener){
mListener = listener;
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener(){
//单击事件
@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;
}
//长按事件
@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) {
//把事件交给GestureDetector处理
if(mGestureDetector.onTouchEvent(e)){
return true;
}else
return false;
}

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

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}


如上所示,使用了GestureDetector类,在事件处理过程中将事件拦截,然后交由GestureDetector判断事件类型,将点击及长按事件交由listener去处理,我们的处理必然是写在MainActivity中咯,具体实现如下:

public class MainActivity extends AppCompatActivity {

private RecyclerView mMyRecyclerView;
private MyRecyclerAdapter mMyRecyclerAdapter;
private ArrayList<String> mData;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mMyRecyclerView = (RecyclerView) findViewById(R.id.rv_my_recycler);
mMyRecyclerAdapter = new MyRecyclerAdapter(this, getData());
mMyRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mMyRecyclerView.setAdapter(mMyRecyclerAdapter);
mMyRecyclerView.addOnItemTouchListener(new RecyclerViewTouchListener(MainActivity.this, mMyRecyclerView, new RecyclerViewTouchListener.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "Click " + mData.get((int)view.getTag()), Toast.LENGTH_SHORT).show();
}

@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this, "LongClick " + mData.get((int)view.getTag()), Toast.LENGTH_SHORT).show();
}
}));
}

public ArrayList<String> getData() {
mData = new ArrayList<>();
for (int i = 0; i < 50; i ++) {
mData.add("item " + i);
}
return mData;
}
}


如上所示,调用addOnItemTouchListener,定义长按及点击事件处理函数,这个方法可以将点击及长按事件独立封装出来,代码结构较好,但是这种实现仅适用于整个Item的点击及长按事件处理,对item中的子View就爱莫能助了,这个方法不是特别推荐,利用事件拦截来进行处理如果一涉及到子view等,这个方法就变得不那么优雅了,接下来的方法三更为推荐,并且不存在方法二的这些问题。方法二的具体实现效果就不上图了,有兴趣的小伙伴可以自行尝试。

方法三: 独立封装,在attach ItemView时添加监听

这个方法是一个国外开发者提出的实现方法,十分推荐,他的实现方式可以独立封装为一个类,在定义了RecyclerView对象时即可进行点击及长按事件的绑定,此时就可以添加你想要的事件监听,十分方便。我们所有的工作仅是在第一部分的简单的RecyclerView的建立的代码基础上添加一个ItemClickSupport.java类,类定义如下:

public class ItemClickSupport {
private final RecyclerView mRecyclerView;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;

private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
}
};

private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
return false;
}
};

private RecyclerView.OnChildAttachStateChangeListener mAttachListener
= new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(View view) {
if (mOnItemClickListener != null) {
view.setOnClickListener(mOnClickListener);
}
if (mOnItemLongClickListener != null) {
view.setOnLongClickListener(mOnLongClickListener);
}
}

@Override
public void onChildViewDetachedFromWindow(View view) {}
};

private ItemClickSupport(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
mRecyclerView.setTag(R.id.item_click_support, this);
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
}

public static ItemClickSupport addTo(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support == null) {
support = new ItemClickSupport(view);
}
return support;
}

public static ItemClickSupport removeFrom(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support != null) {
support.detach(view);
}
return support;
}

public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
return this;
}

public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
return this;
}

private void detach(RecyclerView view) {
view.removeOnChildAttachStateChangeListener(mAttachListener);
view.setTag(R.id.item_click_support, null);
}

public interface OnItemClickListener {
void onItemClicked(RecyclerView recyclerView, int position, View v);
}

public interface OnItemLongClickListener {
boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
}
}


之后在ids.xml中定义一个id如下:

<resources>
<item name="item_click_support" type="id" />
</resources>


这个id的作用主要是将RecyclerView与自己的ItemClickSupport绑定,在add时绑定,之后在remove时将其解绑定。

最后我们在定义RecyclerView的地方添加这个ItemClickSupport对象并将这个RecyclerView与ItemClickSupport进行相应的绑定并定义相应的点击及长按事件即可,这部分的工作我们放在 MainActivity中,具体实现如下所示:

public class MainActivity extends AppCompatActivity {

private RecyclerView mMyRecyclerView;
private MyRecyclerAdapter mMyRecyclerAdapter;
private ArrayList<String> mData;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mMyRecyclerView = (RecyclerView) findViewById(R.id.rv_my_recycler);
mMyRecyclerAdapter = new MyRecyclerAdapter(this, getData());
mMyRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mMyRecyclerView.setAdapter(mMyRecyclerAdapter);

ItemClickSupport.addTo(mMyRecyclerView).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
Toast.makeText(MainActivity.this, "Click " + mData.get(position), Toast.LENGTH_SHORT).show();
}
}).setOnItemLongClickListener(new ItemClickSupport.OnItemLongClickListener() {
@Override
public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
Toast.makeText(MainActivity.this, "LongClick " + mData.get(position), Toast.LENGTH_SHORT).show();
return true;
}
});
}

public ArrayList<String> getData() {
mData = new ArrayList<>();
for (int i = 0; i < 50; i ++) {
mData.add("item " + i);
}
return mData;
}

@Override
protected void onDestroy() {
super.onDestroy();
ItemClickSupport.removeFrom(mMyRecyclerView);
}
}


具体的点击长按效果如下所示:

点击效果:



长按效果



个人认为这个方法最优雅并且这种定义不需要将逻辑写在adapter中,不会造成adapter类处理过多的逻辑,可以专心与adapter原有功能的处理,因此这种方法极为推荐。

至此我们的item的点击及长按实现完成,最后希望大家读有所获。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息