item的点击事件
在RecyclerView中,并没有直接提供类似setOnItemClickListener( )和setOnItemLongClickListener ( )的方法,但是我们可以自己去添加。实现Item的点击事件有三种方式:
- item的布局文件添加点击事件
- 在创建
ItemView
时添加点击监听 - 通过
RecyclerView
已有的方法addOnItemTouchListener()
实现 - 当
ItemView
attachRecyclerView
时实现
介绍四种方式来添加RecyclerView item的点击事件,点击效果如下
看这里👀 item内部的点击事件
# 1.条目的布局文件中添加onClick属性来实现
1. 为item的根布局文件添加click属性
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="itemClick"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="110dp"
android:background="#FFFFFF"
app:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/itemContent"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginLeft="10dp"
android:gravity="center"
android:text="test"
android:textSize="20sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2. 在Activity中实现点击事件即可
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var mRecyclerView: RecyclerView? = null
private var mAdapter: CommonAdapter? = null
private val mDatas: MutableList<String> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
initData()
val layoutManager = LinearLayoutManager(this)
mRecyclerView!!.layoutManager = layoutManager
mAdapter = CommonAdapter(this)
mAdapter!!.setData(mDatas)
mRecyclerView!!.adapter = mAdapter
}
/**
* @param view 就是我们点击的itemView
*/
fun itemClick(view: View?) {
// 获取itemView的位置
val position = view?.let { mRecyclerView!!.getChildAdapterPosition(it) }
// val position = viewHolder?.adapterPosition
val intent = Intent(this@MainActivity, PageActivity::class.java)
intent.putExtra("itemName", mDatas[position!!])
startActivity(intent)
}
private fun initView() {
mRecyclerView = findViewById(R.id.recycler_view)
}
private fun initData() {
for (i in 0..100) {
mDatas.add("Android🎈🎈🎈 - 00$i")
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
这种方式处理item的点击事件极其简单,但是onClick属性必须在包含该组件的Activity中去实现,在Fragment里面就不行了......当然,我们可以通过反射去实现,但是这就太麻烦了
# 2. 在Adapter中提供回调来实现item的点击事件(最常用)
在Activity中
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var mRecyclerView: RecyclerView? = null
private var mAdapter: CommonAdapter? = null
private val dataList: MutableList<String> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
initData()
val layoutManager = LinearLayoutManager(this)
mRecyclerView!!.layoutManager = layoutManager
mAdapter = CommonAdapter(this)
mAdapter!!.setData(dataList)
mRecyclerView!!.adapter = mAdapter
// 真正处理item点击事件
mAdapter!!.setOnItemClickListener(object : CommonAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
val intent = Intent(this@MainActivity, PageActivity::class.java)
intent.putExtra("itemName", dataList[position])
startActivity(intent)
}
})
}
private fun initView() {
mRecyclerView = findViewById(R.id.recycler_view)
}
private fun initData() {
for (i in 0..100) {
dataList.add("Android🏖️ - 00$i")
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Adapter中
class CommonAdapter(private val mContext: Context) : RecyclerView.Adapter<CommonAdapter.ViewHolder>() {
private var mList: List<String>? = null
private val inflater: LayoutInflater = LayoutInflater.from(mContext)
// 声明回调接口
private var onClickListener: OnItemClickListener? = null
fun setData(list: List<String>?) {
mList = list
}
/**
* 设置回调接口
* @param clickListener OnItemClickListener?
*/
fun setOnItemClickListener(clickListener: OnItemClickListener?) {
onClickListener = clickListener
}
/**
* 创建ViewHolder实例
* @param parent
* @param viewType
* @return
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View = inflater.inflate(R.layout.list_item_view, null)
return ViewHolder(view)
}
/**
* 对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行
* @param holder
* @param position
*/
override fun onBindViewHolder(holder: CommonAdapter.ViewHolder, position: Int) {
val itemData = mList!![position]
holder.textView.text = itemData
// 通过为条目设置点击事件触发回调
holder.itemView.setOnClickListener {
Toast.makeText(mContext, "item click", Toast.LENGTH_SHORT).show()
onClickListener!!.onItemClick(holder.adapterPosition)
}
}
override fun getItemCount(): Int {
return if (mList == null) 0 else mList!!.size
}
// 定义回调接口
interface OnItemClickListener {
fun onItemClick(position: Int)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
@JvmField
var textView: TextView = itemView.findViewById(R.id.itemContent)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 3. 通过RecyclerView.addOnItemTouchListener去监听
RecyclerView中有一个监听触摸事件方法,该方法需要一个OnItemTouchListener
对象,我们可以通过这个方法来处理item的点击事件。此方法需要传入一个OnItemTouchListener
,OnItemTouchListener代码如下:
通过注释我们可知,此方法是在滚动事件之前调用 ,需要传入一个
OnItemTouchListener
对象 。 OnItemTouchListener
的代码如下 :
public static interface OnItemTouchListener {
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
public void onTouchEvent(RecyclerView rv, MotionEvent e);
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
2
3
4
5
6
7
8
同时,RecyclerView还提供了一个实现类SimpleOnItemTouchListener
:
在触摸接口中,当触摸时会回调一个MotionEvent对象,通过使用GestureDetectorCompat
来解析用户的操作。
# 3.1 了解GestureDetector的工作原理
对于触摸屏,其原生的消息无非按下、抬起、移动这几种,我们只需要简单重载onTouch或者设置触摸侦听器setOnTouchListener即可进行处理。不过,为了提高我们的APP的用户体验,有时候我们需要识别用户的手势,Android给我们提供的手势识别工具GestureDetector就可以帮上大忙了。
GestureDetector的工作原理是,当我们接收到用户触摸消息时,将这个消息交给GestureDetector去加工,我们通过设置侦听器获得GestureDetector处理后的手势。
GestureDetector提供了两个侦听器接口,OnGestureListener处理单击类消息,OnDoubleTapListener处理双击类消息。
OnGestureListener的接口有这几个:
// 单击,触摸屏按下时立刻触发
abstract boolean onDown(MotionEvent e);
// 抬起,手指离开触摸屏时触发(长按、滚动、滑动时,不会触发这个手势)
abstract boolean onSingleTapUp(MotionEvent e);
// 短按,触摸屏按下后片刻后抬起,会触发这个手势,如果迅速抬起则不会
abstract void onShowPress(MotionEvent e);
// 长按,触摸屏按下后既不抬起也不移动,过一段时间后触发
abstract void onLongPress(MotionEvent e);
// 滚动,触摸屏按下后移动
abstract boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
// 滑动,触摸屏按下后快速移动并抬起,会先触发滚动手势,跟着触发一个滑动手势
abstract boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OnDoubleTapListener的接口有这几个:
// 双击,手指在触摸屏上迅速点击第二下时触发
abstract boolean onDoubleTap(MotionEvent e);
// 双击的按下跟抬起各触发一次
abstract boolean onDoubleTapEvent(MotionEvent e);
// 单击确认,即很快的按下并抬起,但并不连续点击第二下
abstract boolean onSingleTapConfirmed(MotionEvent e);
2
3
4
5
6
有时候我们并不需要处理上面所有手势,方便起见,Android提供了另外一个类SimpleOnGestureListener
实现了如上接口,我们只需要继承SimpleOnGestureListener然后重载需要的手势即可。
# 3.2 实现点击事件监听
1. 定义接口OnItemTouchListener
了解Android事件分发机制的肯定对这几个方法很熟悉,第一个方法是拦截触摸事件的,第二个方法是处理触摸事件的,第三个方法是处理触摸冲突的。此接口官方提供了一个实现类SimpleOnItemTouchListener,不过没有处理什么逻辑。
interface OnItemTouchListener {
/**
* 拦截触摸事件
* @param rv RecyclerView?
* @param e MotionEvent?
* @return Boolean
*/
fun onInterceptTouchEvent(rv: RecyclerView?, e: MotionEvent?): Boolean
/**
* 处理触摸事件
* @param rv RecyclerView?
* @param e MotionEvent?
*/
fun onTouchEvent(rv: RecyclerView?, e: MotionEvent?)
/**
* 处理触摸冲突
* @param disallowIntercept Boolean
*/
fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2. 继承OnItemTouchListener
这里我们通过继承SimpleOnItemTouchListener 来实现我们的点击事件。
首先写一个SimpleRecycleViewItemClickListener类继承SimpleOnItemTouchListener,构造时传入Item点击回调OnItemClickListener,并覆写父类的重写onInterceptTouchEvent
方法,在onInterceptTouchEvent中进行事件处理;
此处是用抽象类来实现,接口实现参考🔗SimpleItemClickListener (opens new window)
abstract class ItemClickListener(private val mRecyclerView: RecyclerView) : SimpleOnItemTouchListener() {
private val mGestureDetectorCompat: GestureDetectorCompat
/**
* 将事件交给GestureDetectorCompat处理
* 并将MotionEvent 传入GestureDetectorCompat使得可以获取触摸的坐标
*/
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
mGestureDetectorCompat.onTouchEvent(e)
}
/**
* 不拦截触摸事件,将事件交给GestureDetectorCompat处理
* @param rv RecyclerView
* @param e MotionEvent
* @return Boolean
*/
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
mGestureDetectorCompat.onTouchEvent(e)
return false
}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
/**
* 定义一个抽象回调方法
* @param vh ViewHolder?
*/
abstract fun onItemClick(vh: RecyclerView.ViewHolder?)
abstract fun onItemLongClick(vh: RecyclerView.ViewHolder?)
abstract fun onItemDoubleClick(vh: View?)
private inner class MyGestureListener : SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean {
val childView = e?.let { mRecyclerView.findChildViewUnder(it.x, e.y) }
if (childView != null) {
val viewHolder = mRecyclerView.getChildViewHolder(childView)
onItemClick(viewHolder) //触发回调
}
return false
}
/**
* 长按事件
* @param e MotionEvent
*/
override fun onLongPress(e: MotionEvent?) {
super.onLongPress(e)
val childView = e?.let { mRecyclerView.findChildViewUnder(it.x, e.y) }
if (childView != null) {
val viewHolder = mRecyclerView.getChildViewHolder(childView)
onItemLongClick(viewHolder) //触发回调
}
}
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
val action = e?.action;
if (action == MotionEvent.ACTION_UP) {
val childView = e.let { mRecyclerView.findChildViewUnder(it.x, e.y) }
if (childView != null) {
onItemDoubleClick(childView);
return true;
}
}
return false
}
}
// 通过构造传入我们的RecyclerView,并初始化GestureDetectorCompat
init {
mGestureDetectorCompat = GestureDetectorCompat(
mRecyclerView.context, MyGestureListener())
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
RecyclerView提供了findChildViewUnder
方法,我们可以通过事件点击的坐标获取点击的Item,再通过RecyclerView的getChildViewHolder
方法获取Item的ViewHolder,进而执行我们想要的操作。
在GestureDetectorCompat
的手势回调中我们覆写:
boolean onSingleTapUp(MotionEvent e)
单击抬起回调void onLongPress(MotionEvent e)
长按回调
3. 在Activity中调用addOnItemTouchListener()
方法
在RecyclerView
的对象中添加addOnItemTouchListener()
方法,然后在回调中处理需要的事件:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var mRecyclerView: RecyclerView? = null
private var mAdapter: CommonAdapter? = null
private val mDatas: MutableList<String> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
initData()
val layoutManager = LinearLayoutManager(this)
mRecyclerView!!.layoutManager = layoutManager
mAdapter = CommonAdapter(this)
mAdapter!!.setData(mDatas)
mRecyclerView!!.adapter = mAdapter
mRecyclerView!!.addOnItemTouchListener(object : ItemClickListener(mRecyclerView!!) {
override fun onItemClick(viewHolder: RecyclerView.ViewHolder?) {
val position = viewHolder?.adapterPosition
val intent = Intent(this@MainActivity, PageActivity::class.java)
intent.putExtra("itemName", mDatas[position!!])
startActivity(intent) // 跳转新Activity,将item的数据传入新Activity
}
})
}
private fun initView() {
mRecyclerView = findViewById(R.id.recycler_view)
}
private fun initData() {
for (i in 0..100) {
mDatas.add("Android🏖️ - 00$i")
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 4. 在Item添加到RecyclerView时添加点击事件
RecyclerView还提供了一个addOnChildAttachStateChangeListener
方法,可以为RecyclerView提供一个OnChildAttachStateChangeListener
,监听一个View添加到RecyclerView和离开RecyclerView,我们可以在这个时候执行一些操作来实现Item点击:
OnChildAttachStateChangeListener listener = new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
final RecyclerView.ViewHolder viewHolder = mRecyclerView.getChildViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null){
mListener.onItemClick(viewHolder);
}
}
});
}
@Override
public void onChildViewDetachedFromWindow(@NonNull View view) {}
}
// ...
mRecyclerView.addOnChildAttachStateChangeListener(listener);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 自定义的条目点击事件支持类ItemClickSupport
class ItemClickSupport public constructor(private val mRecyclerView: RecyclerView) {
private var mOnItemClickListener: OnItemClickListener? = null
private var mOnItemLongClickListener: OnItemLongClickListener? = null
private val mOnClickListener = View.OnClickListener { v ->
if (mOnItemClickListener != null) {
val holder = mRecyclerView.getChildViewHolder(v)
mOnItemClickListener!!.onItemClicked(mRecyclerView, holder.adapterPosition, v)
}
}
private val mOnLongClickListener = OnLongClickListener { v ->
if (mOnItemLongClickListener != null) {
val holder = mRecyclerView.getChildViewHolder(v)
return@OnLongClickListener mOnItemLongClickListener!!.onItemLongClicked(mRecyclerView,
holder.adapterPosition, v)
}
false
}
private val mAttachListener: OnChildAttachStateChangeListener =
object : OnChildAttachStateChangeListener {
override fun onChildViewAttachedToWindow(view: View) {
if (mOnItemClickListener != null) {
view.setOnClickListener(mOnClickListener)
}
if (mOnItemLongClickListener != null) {
view.setOnLongClickListener(mOnLongClickListener)
}
}
override fun onChildViewDetachedFromWindow(view: View) {}
}
fun setOnItemClickListener(listener: OnItemClickListener?): ItemClickSupport {
mOnItemClickListener = listener
return this
}
fun setOnItemLongClickListener(listener: OnItemLongClickListener?): ItemClickSupport {
mOnItemLongClickListener = listener
return this
}
public fun detach(view: RecyclerView) {
view.removeOnChildAttachStateChangeListener(mAttachListener)
view.setTag(R.id.item_click_support, null)
}
interface OnItemClickListener {
fun onItemClicked(recyclerView: RecyclerView?, position: Int, v: View?)
}
interface OnItemLongClickListener {
fun onItemLongClicked(recyclerView: RecyclerView?, position: Int, v: View?): Boolean
}
companion object {
/**
* 为RecyclerView设置ItemClickSupport
* @param view [ERROR : RecyclerView]
* @return ItemClickSupport
*/
fun addTo(view: RecyclerView): ItemClickSupport {
var support = view.getTag(R.id.item_click_support) as? ItemClickSupport
if (support == null) {
support = ItemClickSupport(view)
}
return support
}
/**
* 为RecyclerView移除ItemClickSupport
* @param view [ERROR : RecyclerView]
* @return ItemClickSupport?
*/
fun removeFrom(view: RecyclerView): ItemClickSupport? {
val support = view.getTag(R.id.item_click_support) as? ItemClickSupport
support?.detach(view)
return support
}
}
init {
mRecyclerView.setTag(R.id.item_click_support, this)
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
2. 在values目录下新建一个ids.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>
2
3
4
3. 在Activity中调用
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var mRecyclerView: RecyclerView? = null
private var mAdapter: CommonAdapter? = null
private val mDatas: MutableList<String> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
initData()
val layoutManager = LinearLayoutManager(this)
mRecyclerView!!.layoutManager = layoutManager
mAdapter = CommonAdapter(this)
mAdapter!!.setData(mDatas)
mRecyclerView!!.adapter = mAdapter
ItemClickSupport.addTo(mRecyclerView!!).setOnItemClickListener(object : ItemClickSupport.OnItemClickListener {
override fun onItemClicked(recyclerView: RecyclerView?, position: Int, v: View?) {
val intent = Intent(this@MainActivity, PageActivity::class.java)
intent.putExtra("itemName", mDatas[position])
startActivity(intent)
}
})
}
private fun initView() {
mRecyclerView = findViewById(R.id.recycler_view)
}
private fun initData() {
for (i in 0..100) {
mDatas.add("Android🎨 - 00$i")
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
从以上三种方式的实现过程可知:
- 第一种方式可以很方便获取用户点击的坐标.
- 第二种和第三种方式可以很方便对
ItemView
中的子View
进行监听. - 第一种方式和第三种方式可以写在单独的类中,相对于第二种写在
Adapter
的方式可使代码更独立整洁
📦本文代码DemoItem Link (opens new window)
【文章参考】
- Hugo. Getting your clicks on RecyclerView. https://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/
- 随风飘扬的smile (opens new window).RecyclerView系列之三:处理item的点击事件. https://www.jianshu.com/p/971396467a62
- DevWiki (opens new window). 三种方式实现 RecyclerView 的 Item 点击事件. https://juejin.cn/post/6844903439655239688