`
Z_萧晓
  • 浏览: 11266 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

教你用两招就完美解决 Android 滑动冲突!

阅读更多

冲突情况

在 Android 开发中,滑动冲突总是我们一个无法避免的话题。而对于解决方案却是众说纷纭。比如 RecyclerView 嵌套 RecyclerView,直接通过相关方法禁掉内部 RecyclerView 的滑动;ScrollView 嵌套 RecyclerView 直接把 ScrollView 替换为 NestedScrollView 等等。

但我们今天要说的是在自定义 View 中遇到滑动冲突时,我们又应该如何处理呢?当然,今天的话题需要 View 的事件分发机制做理论前提。

 

1. 简单介绍 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 开发艺术探索:

 

1.同一个事件序列是指从手指接触屏幕(ACTION_DOWN)的那一刻起,到手指离开屏幕(ACTION_UP)的那一刻结束,中间含不定数量的 ACTION_MOVE 事件。
2.某个 View 一旦决定拦截事件,那么这一个事件序列都只能由它处理,并且它的 onInterceptTouchEvent() 方法也不会再调用。换句话说,比如一个 ViewGroup 里面有数个子 View,一旦 ACTION_DOWN 事件从 Activity 传到这个 ViewGroup 被其拦截,则后续的 MOVE 和 UP 等事件也不会传递到里面的子 View 中。
3.如果一个 View 一旦开始处理事件,如果它不消耗 ACTION_DOWN 事件,即 onTouchEvent()返回为 false,那么同一事件序列中的其他事件也不会再交给它处理,直接会调用其父 View 的 onTouchEvent()。
4.如果 View 不消耗除 ACTION_DOWN 以外的其他事件,那么这个点击事件会消失,此时父元素的 onTouchEvent() 并不会被调用,并且当然 View 可以持续收到后续的事件,最终这些消失的点击事件会传递给 Activity 处理。
5.ViewGroup 默认不拦截事件,View 没有 onInterceptTouchEvent() 方法,一旦有事件传递给它,则直接会调用 onTouchEvent(),并且起默认都会消耗掉事件。除非它是不可点击的(即 clickable 和 longClickable 均为 false)。View 的 longClickable 默认都为 false,而 clickable 分情况,比如 Button 默认为 true,TextView 默认为 false。
6.View 的 enable 属性不会影响 onTouchEvent() 的默认返回值,哪怕一个 View 是 disable 状态的,只要它的 clickable 或者 longClickable 有一个为 true,那么它的 onTouchEvent() 就会返回 true。
7.requestDisallowInterceptTouchEvent() 可以在子元素中干预父元素的事件分发过程,但是无法干预 ACTION_DOWN 事件。
8.事件优先顺序:
setOnTouchListener() => onTouchEvent() => onClickListener()

一不小心发现还是挺多的,当然这些都是结论。

 

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

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

 

2.1 外部拦截法

所谓外部拦截法,顾名思义,就是直接在父容器中直接拦截掉我们的滑动事件,让其不能进入到子元素中,这似乎和我们 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 结束,二者缺一不可。

 

2.2 内部拦截法

内部拦截法相对外部拦截法会复杂一些,所以我们通常来说,都更加推荐用外部拦截法进行处理。不过,内部拦截法依然有着它非常重要的地位,具体情况有可能会遇到。

 

内部拦截法的话,需要 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 的时候结合实际场景也就可以得心应手了。

文章提到的问题你遇到过吗?是怎么解决的?


最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

 

相信它会给大家带来很多收获:

 

上述【高清技术脑图】以及【配套的架构技术PDF】可以 加我WX:X1524478394 免费获取!

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

分享到:
评论

相关推荐

    android完美解决listView与ScrollView滑动冲突

    总之,解决Android中的ListView与ScrollView滑动冲突问题,需要理解事件分发机制,以及掌握各种滚动控件的特性和API。通过合理的设计和编程,可以实现两者无缝协作,提供流畅的用户体验。在提供的压缩包文件...

    ListViewPager_完美解决ListView和ViewPager的滑动冲突

    "ListViewPager"这个解决方案可能是通过一种或多种方式来实现滑动冲突的完美解决,以确保用户在使用应用时能享受到流畅的滑动体验。通过深入理解这些机制,开发者可以更好地应对类似的问题,提高应用的用户体验。

    Android 仿抖音直播滑动清屏,完美解决滑动冲突

    Android 仿抖音直播滑动清屏,完美解决滑动冲突。 一个支持左右拖动清屏的Android控件ClearScreenLayout,仿抖音直播的清屏效果,通过`ViewDragHelper`处理遮罩层滑动事件,扩展`EdgeSize`属性实现空白区域拖动...

    Android滑动冲突的完美解决

    本文将详细介绍如何完美解决Android的滑动冲突问题,针对三种常见的冲突场景提供解决方案。 首先,我们需要理解滑动冲突的本质。滑动冲突通常发生在有多个可滑动组件(如ScrollView、ListView、ViewPager等)相互...

    Android滑动冲突的完美解决方案

    在Android开发中,如果是一些简单的布局,都很容易搞定,但是一旦涉及到复杂的页面,特别是为了兼容小屏手机而使用了ScrollView以后,就会出现很多点击事件的冲突,最经典的就是ScrollView中嵌套了ListView。...

    Android滑动冲突问题的解决方法

    滑动冲突可以说是日常开发中比较常见的一类问题,也是比较让人头疼的一类问题,尤其是在使用第三方框架的时候,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了。 关于滑动冲突 滑动冲突分类 滑动...

    完美解决ViewPager两层嵌套滑动问题

    重写了子viewpager的方法,...解决了父viewpager不能滑动或者子viewpager不能滑动问题。修改了事件的分发。可以使子viewpager滑动到最后一页后直接滑入父viewpager的fragment。修改子viewpager可参考local.xml 。

    Android GridView完美横向滑动 ,并且可限制Gridview显示行数!

    本篇文章将深入探讨如何在Android中实现一个完美的横向GridView,并且限制其显示的行数。 首先,我们要明白,原生的GridView并不支持横向滑动,因此我们需要自定义一个HorizontalGridView。这个自定义的控件通常...

    ScrollView+ListView冲突问题完美解决

    此外,为了更好地管理ScrollView和ListView的组合,我们还可以使用NestedScrollView替代ScrollView,NestedScrollView是Android支持库中的一个组件,它内置了解决滑动冲突的机制。只需要将ListView作为...

    Android高仿广告条用ViewPager实现左右完美无限滑动

    综上所述,"Android高仿广告条用ViewPager实现左右完美无限滑动"涉及到的关键技术包括:ViewPager的使用、数据适配、无限滑动逻辑、自动轮播、触摸反馈以及性能优化。通过这些技术,开发者可以创建出流畅、功能丰富...

    onMeasure简单方法 完美解决ListView与ScollView!

    近期做项目碰到ScrollView与Listview冲突的情况,查看了网上一些解决listview和scollView的冲突的方法,最终选择了重写onMeasure的方法来解决这个问题。 在此对各种方法做一个个人的总结评价。 主要的方法有四种: ...

    安卓图片轮播广告轮播自动滚屏相关-Android高仿广告条用ViewPager实现左右完美无限滑动.zip

    本项目“安卓图片轮播广告轮播自动滚屏相关-Android高仿广告条用ViewPager实现左右完美无限滑动”就是针对这一需求提供的一种解决方案。通过使用ViewPager,我们可以实现平滑的左右滑动效果,同时支持自动滚屏,让...

    自定义ViewPager,完美解决ListView和ScrollView事件冲突

    自定义的ViewPager,可以实现页面之前的相互切换,可以...同时该ViewPager解决了传统ViewPager和ListView,ScrollView滑动冲突问题,当然ListView的滑动冲突实在ScrollView中解决的。实践表明,滑动切换效果较好。

    完美解决recyclerview item上面包含多个edittext导致数据错乱,和滑动卡顿问题

    在Android开发中,RecyclerView是广泛使用的视图回收框架,它能高效地展示大量数据列表。然而,当RecyclerView的Item中包含多个EditText时,可能会遇到一些常见的问题,如数据错乱和滑动卡顿。这些问题主要源于两个...

    ListView水平滑动分页

    3. **处理滑动冲突**:由于ListView原本就是设计为垂直滚动的,当尝试改为水平滚动时,可能会与原生的滑动事件发生冲突。这时,你需要通过监听滑动事件并覆盖默认行为,以确保水平滑动的正常进行。可以使用`...

    Android应用源码之完美!SlidingMenu jar包版demo!不用导包!兼容2.2.zip

    3. 兼容性:SlidingMenu为了确保在低版本的Android系统(如2.2)上也能运行,它可能使用了Android Support Library或自定义的解决方案,以支持一些在旧版本中不被支持的功能。 三、SlidingMenu的使用方法 在jar包...

    解决ViewPager嵌套的滑动问题

    当ViewPager 嵌套时,子ViewPager,不能优先处理滑动事件。 ...此Demo完美解决ViewPager嵌套的滑动问题,但快速滑动父ViewPager,再滑动ViewPager还是被父ViewPager处理了,正常操作,都是正常的。

    android完美的侧拉删除+(a-z)和拼音排序

    "android完美的侧拉删除+(a-z)和拼音排序"这个项目提供了一种高效的解决方案,解决了在列表视图中进行侧拉删除操作时可能出现的滑动冲突问题,同时引入了按字母或拼音顺序对数据进行排序的功能。以下是对这些知识点...

Global site tag (gtag.js) - Google Analytics