每日一问: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 再传递给顶层 DecorView,然后遵循上面的方式一直在最里层 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 事件给自己处理。
至于为什么不去拦截 ACTION_DOWN 和 ACTION_UP,想必大家也清楚了。上面说了,如果拦截了 ACTION_DOWN 事件,那后续的 ACTION_MOVE、ACTION_UP 等其它事件均不会在调用 onInterceptTouchEvent() 方法,会直接交给当前容器处理。而如果我们拦截掉 ACTION_UP 的话,肯定会导致子元素的点击事件无法被处理,因为大家肯定都知道一个点击事件从 ACTION_DOWN 开始,从 ACTION_UP 结束,二者缺一不可。
内部拦截法
内部拦截法相对外部拦截法会复杂一些,所以我们通常来说,都更加推荐用外部拦截法进行处理。不过,内部拦截法依然有着它非常重要的地位,具体情况有可能会遇到。
内部拦截法的话,需要 requestDisallowInterceptTouchEvent() 方法的支持,这个方法是干什么的呢?顾名思义,请求是否不允许拦截事件,其接收一个 boolean 参数,表示是否不允许拦截。
我们直接重写子元素的 dispatchTouchEvent() 方法,得到伪代码如下:
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
ev?.run {
when(action){
MotionEvent.ACTION_DOWN -> parent.requestDisallowInterceptTouchEvent(true)
MotionEvent.ACTION_MOVE ->{
if(满足需要让外部容器拦截事件){
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
return super.dispatchTouchEvent(ev)
}
想必代码也是非常简单易懂的,我们给父容器的 requestDisallowInterceptTouchEvent() 传递的参数代表是否不允许其拦截事件,当参数为 true 的时候代表不允许拦截,为 false 的时候代表拦截。所以看起来和外部拦截法也就如出一辙了。
不过仅仅有这点修改还不够,我们通过前面的理论基础知道,当我们的父容器拦截掉 ACTION_DOWN 事件的时候,所有的事件都无法再传递到子元素中,自然也就不会调用上面我们写的 dispatchTouchEvent() 方法了。所以我们在内部拦截法的时候还需要重写父容器的 onInterceptTouchEvent() 方法。
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
ev?.run {
if (action == MotionEvent.ACTION_DOWN){
return false
}
}
return super.onInterceptTouchEvent(ev)
}
至此,基本介绍了两种处理滑动冲突的解决方案,在自定义 View 的时候结合实际场景也就可以得心应手了。
除了滑动冲突,滑动处理也是一项非常有意思的工作,感兴趣的可以可以参考 NestedScrollingParent2 和 NestedScrollingChild2 哟。
文章参考自:《Android 开发艺术探索》
每日一问:Android 滑动冲突,你们都是怎样处理的的更多相关文章
- (转载)Android滑动冲突的完美解决
Android滑动冲突的完美解决 作者:softwindy_brother 字体:[增加 减小] 类型:转载 时间:2017-01-24我要评论 这篇文章主要为大家详细介绍了Android滑动冲突的完 ...
- 关于Android滑动冲突的解决方法(二)
之前的一遍学习笔记主要就Android滑动冲突中,在不同方向的滑动所造成冲突进行了了解,这样的冲突非常easy理解,当然也非常easy解决.今天,就同方向的滑动所造成的冲突进行一下了解,这里就先以垂直 ...
- Android 滑动冲突处理
要想解决滑动冲突就必须好好理解 Android 的事件分发机制.不了解 Android 事件分发机制的请先参考资料学习一下. 一般有 2 种方法 1 外部拦截法 这个非常简单,因为事件是从父 view ...
- Android滑动冲突解决方法
叙述 滑动冲突可以说是日常开发中比较常见的一类问题,也是比较让人头疼的一类问题,尤其是在使用第三方框架的时候,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了. 关于滑动冲突 滑动冲突分类 ...
- Android滑动冲突解决
(1).场景一:外部滑动方向跟内部滑动方向不一致,比如外部左右滑动,内部上下滑动 ViewPager+Fragment配合使用,会有滑动冲突,但是ViewPager内部处理了这种滑动冲突 如果 ...
- android 滑动冲突
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha 通过move事件的 拦截. 在滑动组件中,重写 在拦截触摸事件的时候 这个方法, 然后 ...
- Android View的事件分发机制和滑动冲突解决方案
这篇文章会先讲Android中View的事件分发机制,然后再介绍Android滑动冲突的形成原因并给出解决方案.因水平有限,讲的不会太过深入,只希望各位看了之后对事件分发机制的流程有个大概的概念,并且 ...
- android 布局 使用 viewPager 时,如何解决 和 子页面 长按滑动 冲突问题
使用 viewPager 时,如何解决 和 子页面 长按滑动 冲突问题. 我的问题原型: 这个问题,我相信遇到的人会比较少,我是在 一个 viewPager 中,其中 一个 fragment 中实现了 ...
- 《Android View 的事件分发和滑动冲突》 —预习资料
1. 阅读书籍<Android开发艺术探索>第三章 2. 提前阅读如下技术文章: http://blog.csdn.net/singwhatiwanna/article/details/3 ...
随机推荐
- MinDoc v0.6 发布,轻量级文档在线管理系统
更新日志 新增 标签功能,可以根据标签组织项目 新增 用户删除功能,删除后的用户项目以及其他数据会自动转移到超级管理员账户上 新增 项目描述支持Markdown语法 优化 项目标签添加效果 优化 登录 ...
- 【全面解禁!真正的Expression Blend实战开发技巧】第十章 FluidMoveBehavior完全解析之三飞出ListBox吧
原文:[全面解禁!真正的Expression Blend实战开发技巧]第十章 FluidMoveBehavior完全解析之三飞出ListBox吧 刚才有人说我的标题很给力,哈哈.那这个标题肯定更给力了 ...
- wpf 换行符
方法1: <TextBlock Text="第一行 第二行"/>文字中间加上: 就可以了.这个方法很有用,也可以用在ToolTip上:<ToolTip Width ...
- CPU和GPU双低效,摩尔定律之后一万倍 ——写于TPU版AlphaGo重出江湖之际
本文来自计算机体系结构专家王逵.他认为,“摩尔定律结束之后,性能提升一万倍”不会是科幻,而是发生在我们眼前的事实. 2008年,<三体2:黑暗森林>里写到: 真的很难,你冬眠后不久 ...
- 分配粒度和内存页面大小(x86处理器平台的分配粒度是64K,内存页是4K,所以section都是0x1000对齐,硬盘扇区大小是512字节,所以PE文件默认文件对齐是0x200)
分配粒度和内存页面大小 x86处理器平台的分配粒度是64K,32位CPU的内存页面大小是4K,64位是8K,保留内存地址空间总是要和分配粒度对齐.一个分配粒度里包含16个内存页面. 这是个概念,具体不 ...
- MySQL 查询缓存 QUERY_CACHE
查询缓存(QueryCache)保存查询返回的完整结果.当查询命中该缓存,MySQL会立即返回结果,跳过解析.优化和执行阶段. 官方在特定环境测试结果(官方文档中有详细说明): 1.如果对某表进行简单 ...
- c# Lambda扩展
扩展类 public static class LinqExtensions { /// <summary> /// 创建lambda表达式:p=>true /// </sum ...
- qt---cdb(Microsoft Console Debugger)调试
支持的调试器 windows系统下主要的调试器: CDB ,只能调试用户程序,只有控制台界面,以命令行形式工作 NTSD, 只能调试用户程序,只有控制台界面,以命令行形式工作 KD,主要用于内核调试, ...
- UItableView UIcollectionView下拉刷新会跳动?看了此篇就能解决这个Bug了
顺序如下: 1.数组添加: for (id model in modellist.list) { IDSCommentWeplayList *commentListModel = [I ...
- FireMonkey下的异形窗体拖动(需要使用FmxHandleToHwnd函数转化一下句柄)
DelphiXE2 Firemoney FMX 的窗体不只是为windows的, 所以很多功能都没有了. 最常见的就是拖拽了 先看 VCL时代 一个经典拖动代码 ReleaseCapture(); S ...