前言

本文需要读者对View事件分发的流程有基本的了解,如果还未完全理解事件分发流程的,需要先学习相关部分内容,这部分可以参考我的上一个博客:

Android View点击事件分发原理,源码解读
https://blog.csdn.net/qq_41872247/article/details/139880308

前提:
现在来说我们如果是使用Google官方的View嵌套,比如ViewPager2,ScrollView,RecyclerView等滑动视图的话,哪怕你出现了两个滑动视图嵌套的情况,一般来说也不会出现滑动冲突的场景,因为Google官方经过多年的迭代之后,对于自带的这些视图常用场景的处理都已经很完善了。

已经实测过不会出现滑动冲突的场景(不分先后顺序):

  1. RecyclerView套RecyclerView
  2. ScrollView套RecyclerView
  3. ViewPager2套RecyclerView
  4. ViewPager2套ScrollView,ScrollView套ViewPager2

所以,想要讲述滑动冲突这个问题怎么解决,最需要的是先有一个冲突的场景案例。

1. 滑动冲突

一般而言,滑动冲突只有以下三种场景。

  1. 内部控件的滑动方向和外部控件的滑动方向不同(比如内部视图是左右滑动,而外部视图是上下滑动)
  2. 内部控件的滑动方向和外部控件的滑动方向相同(比如内部视图和外部视图都是上下滑动)
  3. 前两者结合的嵌套滑动问题。

2. 解决方案

我们在处理滑动冲突的时候,无非都是遵循一个原则:
当用户想要操作里面那个视图的滑动功能时,让里面的视图处理掉滑动点击事件。
当用户想要操作外面那个视图的滑动功能时,让外面的视图处理掉滑动点击事件。

基于这两种场景,我们在处理滑动冲突的时候就有了外部解决法和内部解决法。
对于滑动方向不同的场景,用外部解决法比较容易写代码
对于滑动方向相同的场景,用内部解决法比较容易写代码

2.1 外部解决法

在外部视图的onInterceptTouchEvent进行逻辑判断,如果是父布局需要滑动,就拦截该事件,否则就放过该事件,代码如下:

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
    when (ev?.action) {
        MotionEvent.ACTION_DOWN -> {
            return false
        }
        MotionEvent.ACTION_MOVE -> {
            if (这是父布局的滑动事件) {
                return true
            } else {
                return false
            }
        }
        MotionEvent.ACTION_UP,
        MotionEvent.ACTION_CANCEL -> {
            return false
        }
    }
    return super.onInterceptTouchEvent(ev)
}

2.2 内部拦截法

想要让子布局达成这个条件,就需要两点:

  1. 让子布局处理滑动逻辑的视图,确实的消化掉滑动事件,也就是让内部View处理dispatchTouchEvent这个方法最终return true。
  2. 让父布局跳过处理滑动逻辑的视图,不拦截该事件,好让事件确实的能流到内部的视图中而不是被外部视图直接处理,也就是让父布局的拦截方法onInterceptTouchEvent这个方法return false。

对于内部拦截法而言,由于他不直接修改父布局的onInterceptTouchEvent方法,所以他需要另外一个API:parent.requestDisallowInterceptTouchEvent(),用这个API来变相控制父布局的onInterceptTouchEvent返回true或false

同时要注意,由于点击事件的延续性,无论这个滑动事件最终是父布局处理还是子布局处理,最开始的DOWN事件父布局不要拦截,子布局在DOWN事件固定return true。

他的大体代码结构如下:

override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
    when(event?.action) {
        MotionEvent.ACTION_DOWN -> {
        	parent.requestDisallowInterceptTouchEvent(true)
        }
        MotionEvent.ACTION_MOVE -> {
        	if (这是子布局的滑动事件)
            	parent.requestDisallowInterceptTouchEvent(true)
            else
            	parent.requestDisallowInterceptTouchEvent(false)
        }
        MotionEvent.ACTION_CANCEL,
        MotionEvent.ACTION_UP -> {
            parent.requestDisallowInterceptTouchEvent(false)
        }
    }
   
   // 如果是子布局的滑动事件,一定要保证该方法return true,这证明了子视图确实的消费了该点击事件
   // 这样父布局才不会重复处理该事件,引起滑动冲突
   // 由于点击事件的延续性,DOWN固定return true
   if (这是子布局的滑动事件 || event?.action == MotionEvent.ACTION_DOWN)
       super.dispatchTouchEvent(event)
       return true
   else 
       return super.dispatchTouchEvent(event)
}

参考资料

https://blog.csdn.net/wekajava/article/details/120623229

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部