RecyclerView嵌套RecyclerView的加载更多问题
Posted 汤米粥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RecyclerView嵌套RecyclerView的加载更多问题相关的知识,希望对你有一定的参考价值。
recyclerview嵌套,子层recyclerview加载更多,会导致一直加载的问题,于是指定子层recyclerView的高度,但指定了高度子层的recyclerview就不能滑动,下面这篇文字能了解决这个问题。
https://blog.csdn.net/ganduwei/article/details/125025149
-----------------------------------------------------------------------------------------------------------------------
一、概述
虽然今天我们要说的是Rv嵌套Rv的问题,但多数情况下我们都不会使用Rv嵌套Rv,来实现复杂的列表,而是使用多ItemType实现,可能再复杂点的,配合GridLayoutManager.SpanSizeLookup一起来实现,再高级点的自定义LayoutManager实现。
Rv嵌套Rv会有问题,如果嵌套的Rv高度没有设置明确的值,会一次创建所有的item,造成卡顿。类似我们在NestedScrollView里面嵌套Rv,Rv的高度写的是wrap_content或match_parent,一样的情况。
既然高度不确定,那我们给嵌套的Rv指定高度,不就不会一次创建所有item了吗,可真要是这么做,你就会发现嵌套的Rv无法滑动,只能滑动外部的父Rv。
疑惑为啥在NestedScrollView里面嵌套的Rv指定高度后,Rv是能正常滑动的呢?不用奇怪,NestedScrollView之所以叫这个名字,是因为他本身是支持嵌套滑动的。
我们一般不会使用Rv嵌套Rv,但并不是我们不用就不会出现。
有时你可能遇到一个很老的代码,他就是这么实现的,并且还出现了卡顿问题,需要优化。如果你完全改变实现方式使用多ItemType,那改动肯定会很大。在时间不充裕且不能出新bug的情况下,限制子Rv的高度,应该是最好的办法,只要解决子Rv滑动问题。
或者有时,UI出的某个页面,就必须通过Rv嵌套Rv实现,就像这样:
那我们有办法让子Rv正常滑动吗?办法肯定有:
一种是像NestedScrollView,通过嵌套滑动机制;
另一种是基于传统的事件分发机制,请求父Rv不要拦截事件;
下面我们通过第2种方式实现。
二、实现思路
很明显事件被父Rv全部拦截了,所以子Rv不能滑动。我们的思路是,监听事件,如果手指触摸的是子Rv,并且子Rv能滑动,就告诉父Rv不要拦截事件,由子Rv处理。
思路有了,有几点需要考虑如何实现:
如何监听事件?很容易,通过TouchListener即可。
在哪里监听?直接给子Rv设置OnTouchListener 还是 给父Rv 添加 OnItemTouchListener?可能两个地方都可以,需要去试。我已经试过了,答案是给父Rv 添加 OnItemTouchListener。给子Rv设置setOnTouchListener,似乎可以。但实现后发现子Rv时而可以滑动,时而不可以滑动,可能父Rv优先收到事件,还是会直接拦截事件,压根走不到子Rv的onTouch里面。通过给父Rv设置OnItemTouchListener 能保证item始终能收到点击事件,OnItemTouchListener 对事件的处理优先于父Rv。
如何获取手指触摸位置的子Rv?通过父Rv.findChildViewUnder(x, y) 可以拿到触摸位置的 view,再通过父Rv.getChildViewHolder(view)拿到viewHolder,拿到viewHolder便拿到子Rv了。
如何判断子Rv能不能滑动?通过子Rv.canScrollVertically(1) 方法判断能否向上滑动,返回true能; 通过子Rv.canScrollVertically(-1) 方法判断能否向下滑动,返回true能;
如何告诉父Rv不要拦截事件?通过子Rv.requestDisallowInterceptTouchEvent(true)。
三、核心代码
上面思路是我们的核心,其他的都是类似套路,Adapter,点击事件等等。下面是核心代码,相关注释很明确。
rv.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener()
var viewHolder: ParentViewHolder? = null
var mY = 0f
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean
when (e.action)
MotionEvent.ACTION_DOWN ->
rv.findChildViewUnder(e.x, e.y)?.let //找到手指点击的itemView
val vh = rv.getChildViewHolder(it)//获取点击的viewHolder
(vh as? ParentViewHolder)?.let parentVh ->
val childRv = parentVh.childRv
val isVisible = childRv.visibility == View.VISIBLE//是否可见
val canUpScroll = childRv.canScrollVertically(1)//能否向上滑动
val canDownScroll = childRv.canScrollVertically(-1)//能否向下滑动
if (isVisible && (canUpScroll || canDownScroll)) //可见,并且能滑动,请求父Rv不拦截事件
viewHolder?.childRv?.requestDisallowInterceptTouchEvent(true)
viewHolder = vh
mY = e.y
MotionEvent.ACTION_MOVE ->
val childRv = viewHolder?.childRv ?: return false//item里面的Rv
val diff = mY - e.y
mY = e.y
if (diff >= 0) //手指向上滑动
//RecyclerView.canScrollVertically(1)的值表示是否能向上滚动,false表示已经滚动到底部
if (childRv.canScrollVertically(1)) //子Rv未滑到底部,请求父Rv不拦截事件
childRv.requestDisallowInterceptTouchEvent(true)
else //子Rv滑到底部了,父Rv可以拦截事件
childRv.requestDisallowInterceptTouchEvent(false)
else //手指向下滑动
//RecyclerView.canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部
if (childRv.canScrollVertically(-1)) //子Rv未滑到顶部,请求父Rv不拦截事件
childRv.requestDisallowInterceptTouchEvent(true)
else //子Rv滑到顶部了,父Rv可以拦截事件
childRv.requestDisallowInterceptTouchEvent(false)
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->
viewHolder?.childRv?.requestDisallowInterceptTouchEvent(false)
mY = 0f
viewHolder = null
return false
)
四、完整代码
RvNestedRvActivity
class RvNestedRvActivity : AppCompatActivity(), IActionListener
val adapter = ParentAdapter(this)
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_nested)
supportActionBar?.title = "RvNestedRv"
val rv = findViewById<RecyclerView>(R.id.recyclerView)
rv.layoutManager = LinearLayoutManager(this)
rv.adapter = adapter
rv.setHasFixedSize(true)
val list = ArrayList<ParentBean>()
for (i in 0..90)
val parentBean = ParentBean()
parentBean.name = "Parent $i"
val childList = ArrayList<ChildBean>()
for (jj in 0..50)
val childBean = ChildBean()
childBean.name = "Child i$i-$jj"
childList.add(childBean)
parentBean.childList = childList
list.add(parentBean)
adapter.list.addAll(list)
rv.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener()
var viewHolder: ParentViewHolder? = null
var mY = 0f
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean
when (e.action)
MotionEvent.ACTION_DOWN ->
rv.findChildViewUnder(e.x, e.y)?.let //找到手指点击的itemView
val vh = rv.getChildViewHolder(it)//获取点击的viewHolder
(vh as? ParentViewHolder)?.let parentVh ->
val childRv = parentVh.childRv
val isVisible = childRv.visibility == View.VISIBLE//是否可见
val canUpScroll = childRv.canScrollVertically(1)//能否向上滑动
val canDownScroll = childRv.canScrollVertically(-1)//能否向下滑动
if (isVisible && (canUpScroll || canDownScroll)) //可见,并且能滑动,请求父Rv不拦截事件
viewHolder?.childRv?.requestDisallowInterceptTouchEvent(true)
viewHolder = vh
mY = e.y
MotionEvent.ACTION_MOVE ->
val childRv = viewHolder?.childRv ?: return false//item里面的Rv
val diff = mY - e.y
mY = e.y
if (diff >= 0) //手指向上滑动
//RecyclerView.canScrollVertically(1)的值表示是否能向上滚动,false表示已经滚动到底部
if (childRv.canScrollVertically(1)) //子Rv未滑到底部,请求父Rv不拦截事件
childRv.requestDisallowInterceptTouchEvent(true)
else //子Rv滑到底部了,父Rv可以拦截事件
childRv.requestDisallowInterceptTouchEvent(false)
else //手指向下滑动
//RecyclerView.canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部
if (childRv.canScrollVertically(-1)) //子Rv未滑到顶部,请求父Rv不拦截事件
childRv.requestDisallowInterceptTouchEvent(true)
else //子Rv滑到顶部了,父Rv可以拦截事件
childRv.requestDisallowInterceptTouchEvent(false)
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->
viewHolder?.childRv?.requestDisallowInterceptTouchEvent(false)
mY = 0f
viewHolder = null
return false
)
override fun onParentClick(position: Int, bean: ParentBean)
//展开状态,点击折叠;折叠状态,点击展开
bean.isExpand = !bean.isExpand
//刷新item
adapter.notifyItemChanged(position)
override fun onChildClick(position: Int, bean: ChildBean)
Toast.makeText(this, bean.name, Toast.LENGTH_SHORT).show()
IActionListener
interface IActionListener
fun onParentClick(position: Int, bean: ParentBean)
fun onChildClick(position: Int, bean: ChildBean)
ParentAdapter
class ParentAdapter(private val listener: IActionListener?) : RecyclerView.Adapter<ParentViewHolder>()
val list = ArrayList<ParentBean>()
//子Rv用的缓存池
private val recyclerPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParentViewHolder
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.nested_rv_parent_item, parent, false)
return ParentViewHolder(view, listener)
override fun onBindViewHolder(holder: ParentViewHolder, position: Int)
holder.onBind(list[position])
//让子Rv共用同一个缓存池
holder.childRv.setRecycledViewPool(recyclerPool)
override fun getItemCount() = list.size
ParentBean
class ParentBean
var childList: ArrayList<ChildBean>? = null
var name: String? = null
var isExpand = false
ParentViewHolder
class ParentViewHolder(view: View, private val listener: IActionListener?) :
RecyclerView.ViewHolder(view), View.OnClickListener
val childRv: RecyclerView = view.findViewById<RecyclerView>(R.id.childRv).apply
setHasFixedSize(true)
layoutManager = GridLayoutManager(context, 4).apply
spanSizeLookup
private val textTv = view.findViewById<TextView>(R.id.text)
private val imageIv = view.findViewById<ImageView>(R.id.image)
fun onBind(bean: ParentBean)
textTv.text = bean.name
imageIv.rotation = if (bean.isExpand) 180f else 0f
val childList = bean.childList
if (bean.isExpand && childList != null) //展开状态,显示子Rv
childRv.visibility = View.VISIBLE
var adapter = childRv.adapter
if (adapter is ChildAdapter)
adapter.list.clear()
adapter.list.addAll(childList)
adapter.notifyDataSetChanged()
else
adapter = ChildAdapter(listener).apply
list.addAll(childList)
childRv.adapter = adapter
else //折叠状态,隐藏子Rv
childRv.visibility = View.GONE
textTv.setOnClickListener(this)
itemView.tag = bean
override fun onClick(v: View?)
val bean = itemView.tag as? ParentBean ?: return
when (v?.id)
R.id.text ->
listener?.onParentClick(adapterPosition, bean)
ChildAdapter
class ChildAdapter(private val listener: IActionListener?) : RecyclerView.Adapter<ChildViewHolder>()
val list = ArrayList<ChildBean>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChildViewHolder
Log.i("TAG", "ChildAdapter onCreateViewHolder viewType $viewType")
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.nested_rv_child_item, parent, false)
return ChildViewHolder(view,listener)
override fun onBindViewHolder(holder: ChildViewHolder, position: Int)
Log.i("TAG", "ChildAdapter onBindViewHolder position $position")
holder.onBind(list[position])
override fun getItemCount(): Int
return list.size
ChildBean
class ChildBean
var name: String? = null
ChildViewHolder
class ChildViewHolder(view: View, private val listener: IActionListener?) :
RecyclerView.ViewHolder(view),
View.OnClickListener
private val textTv = view.findViewById<TextView>(R.id.textView)
fun onBind(bean: ChildBean)
textTv.text = bean.name
textTv.setOnClickListener(this)
itemView.tag = bean
override fun onClick(v: View?)
val bean = itemView.tag as? ChildBean ?: return
when (v?.id)
R.id.textView ->
listener?.onChildClick(adapterPosition, bean)
五、不足
不能实现子Rv滑动完之后,父Rv接着滑动
某些情况下(快速滑动),父Rv还是会优先滑动,尽管触摸的是子Rv
…
这些不足可能是传统的事件分发机制无法解决的,要避免这些问题,需要使用嵌套滑动机制实现,后面我会基于这种方式实现。
以上是关于RecyclerView嵌套RecyclerView的加载更多问题的主要内容,如果未能解决你的问题,请参考以下文章