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 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 【学习】Android中View的事件体系(下)——滑动冲突 -> 正文阅读

[移动开发]【学习】Android中View的事件体系(下)——滑动冲突

View的滑动冲突

常见的滑动冲突场景

1.外部滑动方向和内部滑动方向不一致
2.外部滑动方向和内部滑动方向一致
3.上面两种情况嵌套

在这里插入图片描述

1.主要是将ViewPager和Fragment配合使用所组成的页面滑动效果,主流应用几乎都会使用这个效果。可通过左右滑动来切换页面,而每个页面内部往往是ListView。本来是有滑动冲突的,但ViewPager帮我们内部处理了。如果使用的是ScrollView等,就必须手动处理,否则内外两层只有一层能够滑动,因为两者之间的滑动事件有冲突。

2.当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题,当手指开始滑动时,系统无法知道用户想让哪一层滑动。

3.上面两种情况的嵌套,它由几个单一的滑动冲突叠加,只需要分别处理内层,中层,外层的滑动冲突即可。

滑动冲突的处理规则

对于场景1.当用户左右滑动时,需要让外部的View拦截点击事件,当上下滑动时,让内部的View拦截点击事件,判断方向有很多种办法,比如根据滑动路径和水平方向所形成的夹角,水平与数值方向上的速度差也可以

对于场景2.比较特殊,无法根据滑动的角度、距离差和速度差来做判断,但是一般都能在业务上找到突破点,当处于某种状态时需要外部View响应滑动,当处于另一种状态时需要内部View响应另一种滑动

对于场景3.同样只能在业务上找到突破点

滑动冲突的解决方式

针对场景1

1.外部拦截法

点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这种方法比较符合点击事件的分发机制。外部拦截需要重写父容器的onInterceptTouchEvent,在内部做相应的拦截即可

在这里插入图片描述

在这里插入图片描述

上图为外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前点击事件这个条件即可。在onInterceptTouchEvent方法中,首先是ACTION_DOWN事件,父容器必须返回false,即不拦截ACTION_DOWN事件,因为一旦父容器拦截DOWN事件,那么后续的MOVE和UP事件都会直接交给父容器处理,这个时候事件没法再传递给子元素。其次是ACION_MOVE事件,这个事件可以根据需要来决定是否拦截,如果父容器需要拦截就返回true,否则返回false。最后是ACTION_UP事件,这里必须要返回false,因为该事件本身没有多大意义。

假设事件交由子元素处理,如果父容器在UP事件时返回了true,就会导致子元素无法接收到UP事件,这个时候子元素的onClick事件就无法触发,但是父容器比较特殊,一旦它开始拦截任何一个事件,那么后续的事件都会交给它来处理,而UP事件作为最后一个事件也必定可以传递给父容器,即使父容器的onInterceptTouchEvent方法在UP时返回了false

2.内部拦截法

内部拦截法指父容器不拦截任何事件,所有的时间都传递给子元素,如果子元素需要此事件就消耗掉,否则就交由父容器进行处理,这种方法和Android中事件分发机制不一致,需要配合requestDisallowTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。我们需要重写子元素的idspatchTouchEvent方法。见下图

在这里插入图片描述

在这里插入图片描述

上述代码是内部拦截法的典型代码,当面对不同滑动策略时只需要修改里面的条件即可。除了子元素需要做处理以外,父元素也要默认拦截除了DOWN事件以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需事件。父容器是不能拦截ACTION_DOWN事件的,因为DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的时间都无法传递到子元素中去,这样内部拦截就无法起作用了。

探究requestDisallowInterceptTouchEvent方法


        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

官方用了mGroupFlags和FLAG_DISALLOW_INTERCEPT按位运算,最终得到(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0这一bool值来标识是否拦截。mGroupFlags是一个16机制整型,可以标识很多状态,每一位标识一个状态,FLAG_DISALLOW_INTERCEPT的值为0x800000,也就是1000000000000000000,它们进行与运算只有第20位有效,因为其他全是0。

回顾dispatchTouchEvent部分源码

// Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
  // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
 
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

dispathEvent每次接收到点击事件时,会初始化触摸状态,然后判断disallowIntercept是否为true,如果为true不执行onInterceptTouchEvent。

探究resetTouchState方法

/**
     * Resets all touch state in preparation for a new cycle.
 
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//重置标识为false
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

它重置了mGroupFlags标识为false,这就解释了为什么在子View中的构造方法或生命周期方法调用parent.requestDisallowInterceptTouchEvent会失效

父元素作如下修改
!在这里插入图片描述

在这里插入图片描述

考虑一种情况,当用户正在水平滑动,但是在水平滑动之前如果用户再迅速进行竖直滑动,就会导致界面在水平方向无法滑动到终点而处于一种中间状态,为了避免这种不良体验,当水平方向正在滑动时,下一个序列的点击事件仍然交给父容器处理,这样水平方向就不会停留在中间状态了。abortAnimation用于优化滑动体验。

而场景2的解决办法和场景1一样,只是滑动规则不同。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-15 22:41:49  更:2022-03-15 22:42:52 
 
开发: 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/24 17:59:32-

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