NestedScrollView RecycleView 嵌套 滑动冲突

2023-11-13

NestedScrollView RecycleView 嵌套 滑动冲突

场景描述

使用NestedScrollView 内嵌RecycleView时,当用户上滑时,NestedScrollView需要首先响应上滑事件,直到ScrollView无法滑动,再由RecycleView进行响应滑动事件

效果演示

请添加图片描述

实现思路

参考谷歌开发者文档中关于view group事件分发的思路,自定义CustomScrollView继承 NestedScrollView 重写onInterceptTouchEvent方法,在拦截到上滑事件时,判断当前的scrollerView是否已经滑动到顶部;

如果ScrollView没有处于顶部,返回true,代表处理并消耗后续一系列触摸事件,包括一系列的Action Move获取到的point到Action Up事件,这中间的事件值都交给 ScrollView处理滑动;
如果ScrollView已经处于顶部,不再拦截上滑事件,会由NestedScrollView默认分发给子控件RecycleView进行处理

/**
 * @author:eric
 * @date:5/31/22
 */
public class CustomScrollView extends NestedScrollView {

    public CustomScrollView(@NonNull Context context) {
        super(context);
    }

    private float maxSlideDis;//向上滑动的最大滑动距离,没有超过这个距离时,拦截并处理掉向上滑动的事件
    //在activity或fragment中,根据布局参数进行设置

    private float mDownY;
    private float mSlop;

    public CustomScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public void setMaxSlideDis(float maxSlideDis) {
        this.maxSlideDis = maxSlideDis;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Logger.log("CustomScrollView onInterceptTouchEvent " + ev.getAction());
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getRawY();
                break;

            case MotionEvent.ACTION_MOVE:
                float dis = ev.getRawY() - mDownY;
                if (dis < 0 && Math.abs(dis) >= mSlop) {
                    //当触摸事件是向上滑动并且滑动距离超过屏幕的最小滑动单位时
                    return needScrollParent();
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    //scroller 是否已经滑动到了最高点
    public boolean needScrollParent() {
        Logger.log("CustomScrollView maxSlideDis = " + maxSlideDis + " getScrollY =" + getScrollY());
        return getScrollY() < maxSlideDis;
    }

    public CustomScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

}

问题和优化

到这一步已经实现先滑动scrollerView再滑动recycleView,但是存在一个问题,当上滑scrollerView的过程中,当scroll view滑动到最顶部不松手指,recycleView不会开始向上滚动,必须等手指离开屏幕,再重新上滑recycle view才可以让其滚动,就像上文gif所展示的;

要明白这个问题的原因,首先要搞清楚一个事件系列 和 view分发机制流程

事件系列: 从ACTION_DOWN 到ACTION_UP 中包含的所有事件,包括其中的所有的ACTION_MOVE事件,为一个事件系列

对view事件分发机制不熟悉的同学可以看下这个分发机制伪代码

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

onInterceptTouchEvent方法要注意: 接收到某一个事件系列中的事件值后,如果返回false或者true,那么后续都不再回调这个方法,这个true或false所代表的状态会持续到这个事件系列结束

因此scroller view的onInterceptTouchEvent 返回true拦截了ACTION_MOVE的前几个事件值后,该事件系列的后续事件值也会直接被scroller view的onTouch消耗掉, 并且onInterceptTouchEvent不再会收到该事件系列的后续ev值的回调,因为在dispatchTouchEvent中,直接将后续事件给到了onTouch进行消耗;

导致后续的滑动事件一直被scrollerview所消耗,

而我们想要下面这样丝滑的滑动体验,就要将scroller view滑动到顶部后的后续事件值分发给子视图进行处理:

在一次滑动事件中完成从scroller view到recycleveiw的滑动,手指不离开屏幕

请添加图片描述

优化

鉴于上述问题,我们把事件的“拦截”放到scroller的dispatchTouchEvent方法中;因为view分发机制首先将每个事件值都传入到dispatchTouchEvent,并在其中通过onInterceptTouchEvent 的返回值来决定是否将该事件分发给子视图的dispatchTouchEvent;

如果scroller view需要滑动,就直接调用onTouch进行消耗事件,如果scroller view
不需要滑动了,就让dispatchTouchEvent默认调用到onInterceptTouchEvent来判断后续事件,这里onInterceptTouchEvent中判断如果scroller view不需要滑动,直接返回false,name后续的ACTION_MOVE值都会被分发给子veiw处理

代码:

 @Override
 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Logger.log("CustomScrollView dispatchTouchEvent ACTION  " + ev.getAction());

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getRawY();
                break;

            case MotionEvent.ACTION_MOVE:
                float dis = ev.getRawY() - mDownY;
                if (dis < 0 && Math.abs(dis) >= mSlop) {
                    //当触摸事件是向上滑动并且滑动距离超过屏幕的最小滑动单位时
                    if (needScrollParent()) {
                        onTouchEvent(ev);
                        Logger.log("dispatchTouchEvent 需要拦截");
                        return true;
                    } else {
                        Logger.log("dispatchTouchEvent 不需要拦截");
                        //这里返回 super.dispatchTouchEvent(ev); 在super.dispatchTouchEvent(ev)中会调用下面的onInterceptTouchEvent 方法来判断
                    }
                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Logger.log("CustomScrollView onInterceptTouchEvent " + ev.getAction());
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getRawY();
                break;

            case MotionEvent.ACTION_MOVE:
                float dis = ev.getRawY() - mDownY;
                if (dis < 0 && Math.abs(dis) >= mSlop) {
                    //当触摸事件是向上滑动并且滑动距离超过屏幕的最小滑动单位时,不拦截滑动事件
                    //这里的目的是取消dispatchTouchEvent中对该事件系列的拦截,当scroller view不需要拦截时,
                    //会执行到这里,
                    return false;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

如果对你有帮助请点赞或收藏 让更多人看到
如果有任何问题欢迎评论讨论

参考文档

谷歌Android开发者文档 :在 ViewGroup 中管理轻触事件

一文读懂Android View事件分发机制

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NestedScrollView RecycleView 嵌套 滑动冲突 的相关文章

  • 如何创建在 React-Native 中检测自动位置的地图

    我已经在react native中创建了地图 参考https github com lelandrichardson react native maps https github com lelandrichardson react nat
  • 模拟器中 Google Wear 上的语音识别器没有语音输入

    我试图使用 Google Wear 网站上的自由形式语音输入 在 hello world 示例中 我刚刚添加了对 textView 的单击 它确实从语音意图中调出 立即发言 活动 但模拟器无法检测到我的麦克风发出的任何声音 我使用的是 Ma
  • 如何制作像 Google+ 应用程序中那样的气泡? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 有谁知道如何使用 Google An
  • 通过代码在创建时突出显示 ListView 项目

    我想在创建 listView 时突出显示 ListView 的第一行 0 我尝试了不同的方法 就像您在注释代码中看到的那样 但没有任何效果 这很奇怪 因为 OnItemClickListener 中的突出显示工作正常 它通过 xml 选择器
  • Android ToggleButton 始终检查

    如果切换按钮处于选中或取消选中状态 我想存储在 SharedPreferences 中 toggle setOnCheckedChangeListener new OnCheckedChangeListener public void on
  • 如何在不使用 Firebase 控制台的情况下发送 Firebase 云消息通知?

    我从新的 Google 通知服务开始 Firebase Cloud Messaging 感谢这段代码https github com firebase quickstart android tree master messaging htt
  • 蓝牙 BLE Android 以最大吞吐量写入外设

    我们公司开发了一个具有蓝牙 BLE 的硬件单元 并且我们在 Nexus 7 2013 中有一个服务应用程序 我们希望使用它向该单元发送固件文件 文件最大可达 500kb BT芯片是德州仪器CC2540 我浏览了大量的页面 并扫描了 Stac
  • 如何使用 RecyclerView 创建此布局?

    我正在尝试使用这种类型的布局创建回收器视图 这些项目是字符串 可以以不同的大小出现 我不知道每行中有多少项目 我可以使用 StaggeredGridLayoutManager 来做到这一点吗 该图像只是一个假示例 每行可以有更多项目 您可能
  • 如何在Android中的DataBinding的ViewModel类中获取R.string

    我目前正在使用databinding对于我的 Android 应用程序项目 我想设置error留言在我的CustomTextView from R string txtOldPassWordError并从另一个名为的类中进行设置ViewMo
  • 更改语言 Flutter 的按钮

    我正在 Flutter 中构建一个应用程序 到目前为止 我正在使用 JSON 国际化 其中应用程序的语言基于用户手机中默认的语言 它工作得很好 但我想给用户有机会在不更改手机系统语言设置的情况下更改语言 只需单击按钮 然后应用程序即可更改语
  • Android 和 Google 地图内部片段以及其他控件和 viewpager

    我是android编程新手 我有一个带有 3 个页面 片段 的小应用程序 使用 pageradapter 和 viewpager 在它们之间滑动 其中一个页面包含复选框 和其他控件 和地图 我的问题是程序在启动时崩溃 Fragment co
  • 如何在android中的谷歌地图上聚焦标记

    我只是想知道我们是否可以关注 Android 应用程序中添加的标记 如果是 怎么办 或者有没有其他方法可以完成这项任务 可以说我使用下面的代码添加了一个标记 map addMarker new MarkerOptions title tit
  • 使用 Proguard 通过 Dropbox.com 库混淆 Android 应用程序

    我刚刚创建了一个需要 Dropbox com API 库的 Android 应用程序 我现在尝试在 发布 模式下构建应用程序 并希望在代码上运行混淆器以对其进行混淆 但是 每当我尝试运行 Proguard 时 都会收到以下错误 Progua
  • 如何将 JSON 数据从 Android 发送到 php url?

    我想将登录信息从我的应用程序发送到 php url 因为这我的应用程序将崩溃 任何人都可以帮助我解决这个问题 这是我的服务器登录方法 我想将数据发送到此登录方法 Method public method login Parameters 3
  • 活动加载缓慢

    我的应用程序中有大约 50 个活动 但其中一个活动非常慢 我的意思是当我按下按钮进入该活动时 需要很长时间 有时会出现黑屏 在该活动中 我有一个按钮 如果我单击该按钮 则需要很长时间才能执行 代码很轻 只有一个Button EditText
  • Android 的 Intent 和 Parcelable 对象

    为什么我需要打包我的对象 即使我只需将其发送到同一任务的另一个线程 实际上 我需要打开一个甚至可以在同一线程 主线程 上运行的活动 换句话说 为什么 Google 不提供一个 startActivity 版本 它采用通用对象广告参数而不是捆
  • 文本视图不显示全文

    我正在使用 TableLayout 和 TableRow 创建一个简单的布局 其中包含两个 TextView 这是代码的一部分
  • onPrepareOptionsMenu 复制 ActionBar 中的项目

    当我使用 onPrepareOptionsMenu 添加菜单项时 该菜单项会在操作栏中复制其自身 我正在使用片段并在主要活动的 ActionBar 中创建初始菜单 如下所示 Override public boolean onCreateO
  • 调试android数据绑定?

    谁能告诉我如何调试或找到数据绑定生成的代码 从this https www youtube com watch v NBbeQMOcnZ0链接我发现它生成了所需的代码 我猜您正在寻找自动生成的绑定 java 文件 我也在寻找他们 最后我在这
  • Android AppWidgetManager 方法 updateAppWidget 无法设置意图、加载数据。而且它是随机发生的

    我的小部件由 2 个按钮和一个显示数据的列表视图组成 大多数时候 当调用小部件提供程序的 onUpdate 方法时 一切都会正常加载 每个人都很高兴 但是我注意到有时在调用更新方法后 小部件完全无法加载其数据 列表视图为空 所有按钮均无响应

随机推荐

  • 为什么程序员都想进互联网大公司,看看福利就知道了

    每年年的最后一个月 一年的辛苦不易除了放假就盼着年终奖了 一直以来被外行人称为 钱多话少 的程序员 到了年底也都能拿到不少的年终奖 毕竟都是996拼出来的 那么国内的互联网年终奖能拿到多少呢 年终奖只是一个平均的 因为每个人的入职时间不同
  • linux离线安装glibc.i686

    一 下载相关rpm包 链接 https pan baidu com s 1Of1myRZa2ClrlSYw43OR3Q 提取码 hlsq 二 将相关rpm包复制到服务器上 三 执行sh install sh即可
  • 如果当前node.js版本和项目需要版本不一样,卸载重装其他版本node.js的方法

    其实这种node js版本不一样的问题 可以选择用nvm来管理node js的不同版本 此处仅总结卸载当前版本node js 重新安装所需版本node js的方法 另 现在 用Vite官网里面的 yarn npm等 的方法 创建Vue3项目
  • DOPI EV200轻松跑debain+jupyter

    硬件 DOPI EV200开发板 一张16G的TF卡 一条usb线 安装好RNIDS驱动 并分配IP地址 详细参考玩转rtsp一文章 上电启动后 程序从Nand Flash启动后 文件系统制作在TF卡中 debain jupyter在1 3
  • ST-Link/v2驱动安装

    链接 https pan baidu com s 1ZXAxwZa46z6K8 U3HW0qvg 提取码 ofmu 从连接里拿到一个名为en stsw link009 v2 0 2 zip的压缩包 如上图所示 新建一个文件夹 ST Link
  • 【微信小程序】文章设置

    设置基本字体样式 行高 首行缩进 font size 32rpx line height 1 6em text indent 2em padding 20rpx 0 border bottom 1px dashed var themColo
  • rabbitmq基础7——队列和消息过期时间设置、死信队列、延迟队列、优先级队列、回调队列、惰性队列

    文章目录 一 过期时间 1 1 针对队列设置 1 2 针对消息设置 二 死信队列 2 1 死信交换器 2 2 死信队列原理 2 3 延迟队列 特殊用法 三 优先级队列 3 1 监控页面创建优先级队列 3 2 监控页面创建优先级消息 四 回调
  • 【有限元分析】网格形状和网格尺寸对结果的影响——以矩形杆的静力分析为例

    本文研究了网格形状和网格尺寸对计算结果的影响 现研究一个矩形截面的杆件 如图1 1 对其末端施加两种等效的载荷 在末端面施加remote force 100N的力 如图1 2所示 对杆件进行2种网格形状划分 分别是六面体网格和四面体网格 如
  • 金融术语总结

    洗钱 将犯罪或其他非法违法行为所获得的违法收入 通过各种手段掩饰 隐瞒 转化 使其在形式上合法化的行为 存量客户 某个时间段里原先已有的客户 与新增客户相对应 月活跃用户数量 MAU Monthly Active User MAU 是当月登
  • 网页常用小技巧(JavaScript)

    1 nc ntextmenu window event returnValue false 将彻底屏蔽鼠标右键 table border border td no td table 可用于Table 2 取消选取 防止复制 3 npaste
  • java日志框架详解

    一 日志的概念 日志文件是用于记录系统操作事件的文件集合 可分为事件日志和消息日志 具有处理历史数据 诊断 问题的追踪以及理解系统的活动等重要作用 二 现有的日志框架 JUL java util logging logback log4j
  • Swagger注解详解

    目录 1 Api 2 APiOperation 3 ApiImplicitParams 4 ApiResponses 5 ApiModel 6 ApiModelProperty 这里是说明常用注解的含义和基本用法 也就是说已经对swagge
  • 使用反射对单例模式进行攻击的讨论

    我们都知道在单例模式中 对构造函数进行私有化private修饰 保证了类不能使用new进行对象的实例化 但是如果使用反射获取构造函数 在进行实例化就会导致private失效 作者用中文作为类名 请读者勿怪 纯属喜好 工作中是不允许的哦 ja
  • 为什么我们要考虑线性规划的对偶问题?

    文章转自 https www zhihu com question 26658861 版权归原作者
  • 更改计算机bios密码怎么办,计算机BIOS通用密码的修改

    电脑信息的保密一直是一个重要的话题 众所周知 在BIOS设置菜单中 有两个密码设置栏目 Supervisor Password 超级用户密码 和User Password 一般用户密码 可以说是电脑资料保密的第一道防线 这两组密码搭配BIO
  • 2023届秋招,我重新认清了自己

    仅记录个人经历 充满主观感受 甚至纯属虚构 仅供参考 杠就是你对 本想毕业再写 但是考虑到等毕业了 24秋招的提前批就快开始了 大概就来不及了 正好现在有点时间 陆陆续续的写了出来 个人情况 学历 苏北二本 加南京某211 现在二本三本合并
  • 【转载】PMOS,NMOS

    NMOS保证截止 发生在G电位小于等于S PMOS保证截止 发生在G电位大于等于S 寄生二极管是因为基底与Source连接形成的PN结
  • JQuery获取多个name相同select/input框的value值

    JavaWeb jsp页面 使用c forEach 循环多个select下拉框 name相同获取被选中的值 jsp
  • 虚拟机连接不上网络,解决办法

    虚拟机连接不上网络解决思路 简单的介绍了VM虚拟机常用的三种网络连接方式 一般用NAT方式虚拟机就很容易上网的 所以一般没有特殊要求推荐用NAT方式 1 桥接 就是把虚拟机通过VMnet0桥接到主机的本地连接 现在虚拟机是通过VMnet0与
  • NestedScrollView RecycleView 嵌套 滑动冲突

    NestedScrollView RecycleView 嵌套 滑动冲突 场景描述 效果演示 实现思路 问题和优化 优化 参考文档 场景描述 使用NestedScrollView 内嵌RecycleView时 当用户上滑时 NestedSc