根本原因:
由于Android事件分发机制,父控件会优先拦截事件,会先抢占滑动事件,使子控件无法响应导致。
RecyclerView嵌套在ScrollView中
原因:
- ScrollView会优先处理滑动事件,导致RecyclerView不能正常滑动。
- 会产生性能问题:由于ScrollView会一次性测量所有的子View,而RecyclerView则是通过复用机制,避免一次性加载所有的item。
解决方案:
1:让RecyclerView适配ScrollView滑动。
- 设置RecyclerView的setNestedScrollingEnabled这个属性为false。不让ReclclerView自己滚动,都交给ScrollView来处理滚动事件,但是这样的话会让RecyclerView失去自己的优势。
recyclerView.isNestedScrollingEnabled = false
- 动态计算RecyclerView的高度,由于RecyclerView默认是无限滚动的,当放入ScrollView后不会撑满, 通过动态计算RecyclerView的高度设置FullyExpandedLinearLayoutManager让RecyclerView展开,避免滑动冲突。
kotlin
class FullyExpandedLinearLayoutManager(context: Context) : LinearLayoutManager(context) {
override fun onMeasure(recycler: RecyclerView.Recycler, state: RecyclerView.State, widthSpec: Int, heightSpec: Int) {
val expandedHeightSpec = MeasureSpec.makeMeasureSpec(Int.MAX_VALUE shr 2, MeasureSpec.AT_MOST)
super.onMeasure(recycler, state, widthSpec, expandedHeightSpec)
}
}
然后在 RecyclerView
中使用这个 LayoutManager
recyclerView.layoutManager = FullyExpandedLinearLayoutManager(context)
作用:
让 RecyclerView
适应 ScrollView
,动态测量 RecyclerView
高度,使其完全展开,避免滑动冲突。
总结:这个情况只适用于数据量比较小,不需要分页加载希望整个页面滑动的时候使用。
2:只保留RecyclerView,去掉ScrollView
自定义Adapter处理RecycleView内容
kotlin
class HeaderFooterAdapter(private val context: Context, private val data: List<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val TYPE_HEADER = 0
private val TYPE_ITEM = 1
override fun getItemViewType(position: Int): Int {
return if (position == 0) TYPE_HEADER else TYPE_ITEM
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TYPE_HEADER) {
val view = LayoutInflater.from(context).inflate(R.layout.header_layout, parent, false)
HeaderViewHolder(view)
} else {
val view = LayoutInflater.from(context).inflate(R.layout.item_layout, parent, false)
ItemViewHolder(view)
}
}
override fun getItemCount(): Int = data.size + 1 // 1 for header
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ItemViewHolder) {
holder.textView.text = data[position - 1]
}
}
class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view)
class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_text)
}
}
让 RecyclerView
处理整个页面的滚动,避免多层嵌套引起的性能问题 直接用 RecyclerView
处理所有的内容,包括 Header ,避免 ScrollView
的嵌套,提升流畅度
RecyclerView和ViewPager2嵌套(ViewPager2和ViewPager2嵌套也适用)
原因:
由于ViewPager2内部依然是使用RecyclerView实现的,因此两者嵌套使用会出现滑动冲突问题。
解决方案:
自定一个Layout继承基础Layout控件,通过重写onInterceptTouchEvent方法,在action_down事件中默认让子控件处理滑动事件,在ACTION_MOVE事件中去判断子控件是否滑动到了最左侧或最右侧,如果是的话,将事件交给父控件处理。将这个自定义控件包裹住子控件。
kotlin
class InterceptLayout@JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
private var startX = 0f
private var startY = 0f
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
val recyclerView = getChildAt(0) as? RecyclerView ?: return super.onInterceptTouchEvent(ev)
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
startX = ev.x
startY = ev.y
parent.requestDisallowInterceptTouchEvent(true) // 默认让 RecyclerView 处理滑动
}
MotionEvent.ACTION_MOVE -> {
val dx = ev.x - startX
val dy = ev.y - startY
if (abs(dx) > abs(dy) && abs(dx) > touchSlop) {
if (dx > 0 && !recyclerView.canScrollHorizontally(-1)) {
// ➡️ 向右滑,RecyclerView 已经滚动到最左侧,交给 ViewPager2 处理
parent.requestDisallowInterceptTouchEvent(false)
return false
} else if (dx < 0 && !recyclerView.canScrollHorizontally(1)) {
// ⬅️ 向左滑,RecyclerView 已经滚动到最右侧,交给 ViewPager2 处理
parent.requestDisallowInterceptTouchEvent(false)
return false
}
}
}
}
return super.onInterceptTouchEvent(ev)
}
}
RecyclerView 嵌套 SwipeRefreshLayout
原因:
SwipeRefreshLayout的下拉刷新可能会与RecyclerView滑动冲突
解决方案:
自定义SwipeRefreshLayout控件,重写canChildScrollUp方法
kotlin
class FixedSwipeRefreshLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : SwipeRefreshLayout(context, attrs) {
var targetRecyclerView: RecyclerView? = null
override fun canChildScrollUp(): Boolean {
return targetRecyclerView?.canScrollVertically(-1) ?: super.canChildScrollUp()
}
}
在代码中设置: swipeRefreshLayout.targetRecyclerView = recyclerView
就先这样,只想到这么多了。。。。。。。。。。。。