- 关于在RecyclerView中的Banner滑动冲突问题
- android的RecyclerView嵌套在NestedScr
- android解决RecyclerView嵌套在NestedSc
- 日常bug收集之ScrollView和RecyclerView的
- ViewPager与RecyclerView的滑动冲突问题
- ScrollingView嵌套RecyclerView一系列问题
- Android ScrollView 嵌套RecyclerVie
- 解决BottomSheetDialog与RecyclerView
- RecyclerView的item中嵌套Scrollview的滑
- CoordinatorLayout CollapsingToo
前言
本文主要是为了记录一下在RecyclerView中添加同向滑动的Banner后导致Banner无法切换的问题,此问题的解决方案可能不是最优解,也可能不是你期望的结果,但能够提供一种解决思路,希望对你有用。
(本文以上下滑动的RecyclerView与上下滑动切换的BannerViewPager滑动冲突为示例)
问题复现
View的布局结构是一个全屏的androidx.recyclerview.widget.RecyclerView
,使用androidx.recyclerview.widget.LinearLayoutManager
。其中Item布局xml如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/clTaskRoot"
android:layout_width="match_parent"
android:tag="disable_vertical_scroll"
android:layout_height="wrap_content">
<com.zhpan.bannerview.BannerViewPager
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:bvp_indicator_checked_color="@color/color_FFFFFF"
app:bvp_indicator_normal_color="@color/color_FFFFFF_50"
app:bvp_indicator_style="round_rect"
app:bvp_interval="5000"
app:bvp_scroll_duration="600" />
<com.ned.mysterybox.view.HorizontalIndicatorView
android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="2dp"
android:layout_marginTop="@dimen/dp_8"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/banner"
app:layout_constraintStart_toStartOf="@id/banner"
app:layout_constraintEnd_toEndOf="@id/banner"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
BannerViewPager滑动方向设置为上下滑动setOrientation(ViewPager2.ORIENTATION_VERTICAL)
(注意这边设置了一个tag = disable_vertical_scroll
后面会用到)
运行后出现的问题是,当点击BannerViewPager
向上或者向下滑动Banner切换时,RecyclerView
消耗了所有的滑动事件,并没有按照预期的Banner切换,而是变成了RecyclerView
的上下滑动。
解决方案
其实很容易发现,出现此问题的原因就是Touch事件分发与预期不一致。
思路分析
如果是自己开发的自定义View,可以调整自己Touch事件处理的逻辑代码进行修复,但本次因为使用的是第三方库的原因,代码逻辑已经基本固定,不容易再进行二次开发(也不是不行,可能不是太容易)。那换个思路,在RecyclerView
中进行拦截?
通过分析RecyclerView
的源码可以发现,其其实可以通过设置addOnItemTouchListener
实现对Touch事件的拦截。
OnItemTouchListener
接口有三个函数 onInterceptTouchEvent
、onTouchEvent
、onRequestDisallowInterceptTouchEvent
,很好理解,这些函数名跟ViewGroup中相应的函数名一致,功能也类似。
接下来要做的就是2件事,事件发现
和事件处理
。
1)事件发现
回到我们最初预期的效果,当我们滑动Banner时Banner处理,滑动其他Item时RecyclerView滑动处理。
所以可以认为,当Touch事件的MotionEvent.ACTION_DOWN
在Banner区域时,Banner消耗所有的滑动事件,在其他View区域时,不进行处理。
通过将MotionEvent事件的x,y坐标传入findChildViewUnder
可以找到 RecyclerView
中所对应的View。这里我使用tag标记,判断此View是否就是目标View(也可通过ID或者class名确认),返回True,意味着接下来的所有Touch事件均将被拦截。
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
if (e.action != MotionEvent.ACTION_DOWN) return false
val view = rv.findChildViewUnder(e.x, e.y)
if (view?.tag == "disable_vertical_scroll") {
......
//找到实际的Banner View
targetView = view.findViewById<BannerViewPager<*>>(R.id.banner)
targetView?.requestDisallowInterceptTouchEvent(true)
return true
}
return false
}
2)事件处理
到这里其实就很好处理了, 就是对一连串的Touch事件转换为滑动或者点击动作,并响应。
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
targetView?.dispatchTouchEvent(e)
when (e.action) {
MotionEvent.ACTION_DOWN -> {
startX = e.x
startY = e.y
}
MotionEvent.ACTION_MOVE -> {
process(rv, e)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
process(rv, e)
startX = 0f
startY = 0f
}
}
}
总结一下,实现步骤大致是:
1)RecyclerView
通过设置OnItemTouchListener
监听器实现对在RecyclerView
上所有的Touch事件进行处理(判断是否拦截);
2)在OnItemTouchListener
监听器的onInterceptTouchEvent
函数中,通过RecyclerView
的findChildViewUnder
函数判断是否是目标区域的滑动事件,是返回true表示进行拦截,否返回false表示不进行处理;
3)在OnItemTouchListener
监听器的 onTouchEvent
函数中对所有拦截的Touch事件进行处理,包括上下、左右滑动、点击等。
OK,到这里就已经基本结束,接着就是对一些细节进行优化处理,在文章后面贴上了完整代码。
如果本文对你有帮助就点个赞支持下吧~~~
完整代码
mRecyclerView.addOnItemTouchListener(VerticalScrollBannerTouchListener(mContext))
class VerticalScrollBannerTouchListener(val context: Context) : RecyclerView.OnItemTouchListener {
var targetView: BannerViewPager<*>? = null
var vRoot: View? = null
val mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop / 3
var enable = false
var startX = 0f
var startY = 0f
var processTime = 0L
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
if (e.action != MotionEvent.ACTION_DOWN) return false
val view = rv.findChildViewUnder(e.x, e.y)
if (view?.tag == "disable_vertical_scroll") {
vRoot = view
targetView = view.findViewById<BannerViewPager<*>>(R.id.banner)
targetView?.requestDisallowInterceptTouchEvent(true)
enable = true
return true
}
enable = false
targetView = null
return false
}
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
targetView?.dispatchTouchEvent(e)//不能删
when (e.action) {
MotionEvent.ACTION_DOWN -> {
startX = e.x
startY = e.y
}
MotionEvent.ACTION_MOVE -> {
process(rv, e)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
process(rv, e)
startX = 0f
startY = 0f
if (enable) {
val ec = MotionEvent.obtain(e)
val xx = (rv.scaleX + (vRoot?.left ?: 0) + (targetView?.left ?: 0))
val xy = (rv.scaleY + (vRoot?.top ?: 0) + (targetView?.top ?: 0))
ec.offsetLocation(-xx, -xy)
ec.action = MotionEvent.ACTION_DOWN
targetView?.dispatchTouchEvent(ec)
ec.action = MotionEvent.ACTION_UP
targetView?.dispatchTouchEvent(ec)
ec.recycle()
}
enable = false
targetView = null
}
}
}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
}
fun process(rv: RecyclerView, e: MotionEvent) {
if (!enable) return
val endX = e.x
val endY = e.y
var dy = startY - endY
val disX = abs(endX - startX)
val disY = abs(endY - startY)
if (disY > disX) {
dy = if (dy > 0) {
0f.coerceAtLeast(dy - mTouchSlop)
} else {
0f.coerceAtMost(dy + mTouchSlop)
}
if (dy != 0f) {
val size = targetView?.data?.size ?: 1
val now = System.currentTimeMillis()
if (size > 1 && now - processTime > 600) {
val p = targetView?.currentItem ?: 0
if (dy > 0) {
if (p + 1 >= size) {
targetView?.currentItem = 0
} else {
targetView?.currentItem = p + 1
}
} else {
if (p - 1 < 0) {
targetView?.currentItem = size - 1
} else {
targetView?.currentItem = p - 1
}
}
processTime = System.currentTimeMillis()
}
enable = false
}
}
}
}
网友评论