每日一问:Android 滑动冲突,你们都是怎样处理的

坚持原创日更,短平快的 Android 进阶系列,敬请直接在微信公众号搜索:nanchen,直接关注并设为星标,精彩不容错过。

Android 开发中,滑动冲突总是我们一个无法避免的话题。而对于解决方案却是众说纷纭。比如 RecyclerView 嵌套 RecyclerView,直接通过相关方法禁掉内部 RecyclerView 的滑动;ScrollView 嵌套 RecyclerView 直接把 ScrollView 替换为 NestedScrollView 等等。但我们今天要说的是在自定义 View 中遇到滑动冲突时,我们又应该如何理呢?

当然,今天的话题需要 View 的事件分发机制做理论前提,还不了解 View 的事件分发机制的小伙伴可以移步我之前面试系列的一篇文章:面试系列:讲讲 Android 的事件分发机制。

简单介绍 View 的时间分发机制

当然,这里也可以简单地提一下,基本的流程就是下面的伪代码。

public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if (onInterceptTouchEvent(ev)) { consume = onTouchEvent(ev); }else{ consume = child.dispatchTouchEvent(ev); } return consume; }

当一个 ViewGroup 接收到一个事件的时候,首先会调用 dispatchTouchEvent() 方法进行事件分发,如果 onInterceptTouchEvent() 返回 true,则代表当前 View 会拦截事件,则直接回调 onTouchEvent() 方法进行事件理。如果不拦截,则直接回调子 View 的 dispatchTouchEvent() 方法,如此反复,一直到最里面的子 View。

当一个点击事件产生后,它的传递过程遵循以下顺序:Activity => Window => View,即事件总是先传递给 Activity,Activity 再传递给 Window,最后 Window 再传递给顶层 DocorView,然后遵循上面的方式一直在最里层 View。

而处理事件则从最里层 View 不断回传给自己的外层 View,如果一直没有 View 进行处理,则直接会回传到 Activity 中。

onTouchEvent() 返回 true 代表自己要处理。

既然都提了这么一点,也就突然想给出一些结论,参考自 Android 开发艺术探索:

同一个事件序列是指从手指接触屏幕(ACTION_DOWN)的那一刻起,到手指离开屏幕(ACTION_UP)的那一刻结束,中间含不定数量的 ACTION_MOVE 事件。

某个 View 一旦决定拦截事件,那么这一个事件序列都只能由它处理,并且它的 onInterceptTouchEvent() 方法也不会再调用。换句话说,比如一个 ViewGroup 里面有数个子 View,一旦 ACTION_DOWN 事件从 Activity 传到这个 ViewGroup 被其拦截,则后续的 MOVE 和 UP 等事件也不会传递到里面的子 View 中。

如果一个 View 一旦开始处理事件,如果它不消耗 ACTION_DOWN 事件,即 onTouchEvent() 返回为 false,那么同一事件序列中的其他时间也不会再交给它处理,直接会调用其父 View 的 onTouchEvent()。

如果 View 不消耗除 ACTION_DOWN 以外的其他事件,那么这个点击事件会消失,此时父元素的 onTouchEvent() 并不会被调用,并且当然 View 可以持续收到后续的事件,最终这些消失的点击事件会传递给 Activity 处理。

ViewGroup 默认不拦截事件,View 没有 onInterceptTouchEvent() 方法,一旦有事件传递给它,则直接会调用 onTouchEvent(),并且起默认都会消耗掉事件。除非它是不可点击的(即 clickable 和 longClickable 均为 false)。View 的 longClickable 默认都为 false,而 clickable 分情况,比如 Button 默认为 true,TextView 默认为 false。

View 的 enable 属性不会影响 onTouchEvent() 的默认返回值,哪怕一个 View 是 disable 状态的,只要它的 clickable 或者 longClickable 有一个为 true,那么它的 onTouchEvent() 就会返回 true。

requestDisallowInterceptTouchEvent() 可以在子元素中干预父元素的事件分发过程,但是无法干预 ACTION_DOWN 事件。

事件优先顺序:setOnTouchListener() => onTouchEvent() => onClickListener()

一不小心发现还是挺多的,当然这些都是结论,具体可以跟着 面试系列:讲讲 Android 的事件分发机制 进行源码流程探讨,你会发现上面的结论很容易得到。

处理自定义 View 中的滑动冲突

对于大多数 Android 开发来说,处理滑动冲突好像很难,但实战一下又发现,好像也挺简单,因为这个实际上是有套路可循的。基本就两种方案:外部拦截法 && 内部拦截法。

外部拦截法

所谓外部拦截法,顾名思义,就是直接在父容器中直接拦截掉我们的滑动事件,让其不能进入到子元素中,这似乎和我们 RecyclerView 嵌套 RecyclerView 时禁用内部 RecyclerView 滑动有那么一丝相似之处,就是内部不处理就完事儿了。但细细品来又完全不一样,这里的外部拦截法会让内部元素根本就收不到滑动事件。

这种方法明显非常适合我们上面讲的事件分发机制。我们在接收 ACTION_MOVE 事件的时候,直接通过使 onInterceptTouchEvent() 方法返回 true 来直接拦截掉事件就可以了,伪代码想必大家也知道了:

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { ev?.run { if (action == MotionEvent.ACTION_MOVE && 父容器需要点击事件){ return true } } return super.onInterceptTouchEvent(ev) }

代码很简单,我们仅仅需要在事件 ACTION_MOVE 时去处理我们的逻辑就好了,当满足我们的逻辑的时候,就拦截掉 ACTION_MOVE 事件给自己处理。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjpzg.html