IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> RecyclerView嵌套RecyclerView的滑动问题如何解 -> 正文阅读

[移动开发]RecyclerView嵌套RecyclerView的滑动问题如何解

一、概述

虽然今天我们要说的是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

这些不足可能是传统的事件分发机制无法解决的,要避免这些问题,需要使用嵌套滑动机制实现,后面我会基于这种方式实现。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-06-06 17:28:44  更:2022-06-06 17:29:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 1:31:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码