嵌套滑动学习

2023-11-04

我们知道两个同一方向的可以滑动的View,如果不做任何处理,会出现滑动冲突,处理滑动冲突我们有内部和外部拦截法。

如果有朋友还不知道事件分发的原理可以看包括滑动冲突的拦截事件分发源码的学习分享

处理滑动冲突之后还是存在缺陷,比如一次滑动操作父View消费完之后子View没有办法继续消费,滑动惯性也无法传递,所有就有了:
NestedScrollingParent和NestedScrollingChild
而我们使用的时候是通过NestedScrollingParentHelper和NestedScrollingChildHelper

可滑动的ViewGroup
NestedScrollingParent 充当父View
NestedScrollingChild 充当子View

一个嵌套滑动子View和父View的简单交互流程。

事件的消费是从子View开始的

 case MotionEvent.ACTION_DOWN:{
                mLastTouchY = (int)(event.getRawY() + .5f);
                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;

                nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                //上面判断是Y轴滑动还是X轴滑动,把对应的轴传递进去
                startNestedScroll(nestedScrollAxis);
                break;
            }
@Override
    public boolean startNestedScroll(int axes) {
    	//使用对应的helper来帮助我们调用
        return helper.startNestedScroll(axes);
    }
@Override
    public boolean startNestedScroll(int axes, int type) {
    	//type 做了兼容处理
        return mChildHelper.startNestedScroll(axes, type);
    }
  public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
  		//如果有了能嵌套滑动的父View,直接返回
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        //当前是否支持嵌套滑动
        if (isNestedScrollingEnabled()) {
        	//拿当前View的父View
            ViewParent p = mView.getParent();
            //把当前View赋值为child
            View child = mView;
            //循环遍历往上找,直到找到支持嵌套滑动的父View
            while (p != null) {
            	//如果你是父View:NestedScrollingParent,
            	//onStartNestedScroll方法返回true的话进入if
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                	//将父Viewt的type存储起来,你是直接touch还是Fing
                    setNestedScrollingParentForType(type, p);
                    //将父View嵌套滑动的Y轴还是X轴存储起来
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                //继续往上查找,支持多层嵌套
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }

上面的onStartNestedScroll调用到ViewParentCompat,就是一个兼容处理,后续不再贴出了

  public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
                    nestedScrollAxes, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            if (Build.VERSION.SDK_INT >= 21) {
                try {
                    return parent.onStartNestedScroll(child, target, nestedScrollAxes);
                } catch (AbstractMethodError e) {
                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                            + "method onStartNestedScroll", e);
                }
            } else if (parent instanceof NestedScrollingParent) {
                return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
                        nestedScrollAxes);
            }
        }
        return false;
    }

找到可支持滑动嵌套的父View之后下一步是在move事件中

 case MotionEvent.ACTION_MOVE:{
                int x = (int)(event.getRawX() + .5f);
                int y = (int)(event.getRawY() + .5f);
                int dx = mLastTouchX -x;
                int dy = mLastTouchY -y;
                mLastTouchX = x;
                mLastTouchY = y;
				//将滑动情况传给父View,让父View先决定是否需要消费
				//consumed是数组,0==x轴,1==y轴
                if(dispatchNestedPreScroll(dx,dy,consumed,null)){
                    Log.i("onMeasure","dy: " + dy + ", cosumed: " + consumed[1]);
                    //父View消费了Y轴多少需要减掉
                    dy -= consumed[1];
                    if(dy == 0){
                        Log.i("onMeasure","dy: " + dy);
                        return true;
                    }
                }else{
                    Log.i("onMeasure","scrollBy: " + dy);
                    scrollBy(0,dy);
                    //如果子View没消费完可以再次传给父View  dispatchNestedScroll
                }
                break;
            }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        Log.i(Tag, "dispatchNestedPreScroll:dx" + dx + ",dy:" + dy + ",
        consumed:" + consumed[1] +  ",offsetInWindow:" + offsetInWindow);
        return helper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }
   public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow, @NestedScrollType int type) {
        //再次验证判断是否支持嵌套滑动
        if (isNestedScrollingEnabled()) {
            final ViewParent parent = getNestedScrollingParentForType(type);
            if (parent == null) {
                return false;
            }

            if (dx != 0 || dy != 0) {
                int startX = 0;
                int startY = 0;
                if (offsetInWindow != null) {
                    mView.getLocationInWindow(offsetInWindow);
                    startX = offsetInWindow[0];
                    startY = offsetInWindow[1];
                }
				//上面主要是做一个视差效果处理
                if (consumed == null) {
                    consumed = getTempNestedScrollConsumed();
                }
                //确保consumed不是null
                consumed[0] = 0;
                consumed[1] = 0;
                //将参数传给父View
                ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);

                if (offsetInWindow != null) {
                    mView.getLocationInWindow(offsetInWindow);
                    offsetInWindow[0] -= startX;
                    offsetInWindow[1] -= startY;
                }
                return consumed[0] != 0 || consumed[1] != 0;
            } else if (offsetInWindow != null) {
                offsetInWindow[0] = 0;
                offsetInWindow[1] = 0;
            }
        }
        return false;
    }

父View处理

   public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        boolean show = showImg(dy);
        boolean hide = hideImg(dy);
//        Log.i("onMeasure","show: " + show + ", hide: " + hide);
        if(show||hide){//1.作业 消费过头
            consumed[1] = dy;//全部消费
            scrollBy(0, dy);
            Log.i(Tag,"Parent滑动:"+dy);
        }
        Log.i(Tag, "onNestedPreScroll--getScrollY():" + getScrollY() + ",dx:" + dx + ",dy:" + dy + ",consumed:" + consumed[1]);
    }

最后:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
stopNestedScroll();

从子View的down事件开始我们需要调用startNestedScroll(传入需要嵌套滑动的轴)–》
内部代码找到支持嵌套滑动的父View…–》onNestedPreScroll(父View处理)返回处理结果

其实还有Fing的传递,后面讲,大方向就是类似上面这样。

我们常用的滑动列表的控件有recyclerView

public class RecyclerView extends ViewGroup implements ScrollingView,
        NestedScrollingChild2, NestedScrollingChild3 

可以看出它实现了嵌套滑动的子View

当页面再稍微复杂点,我们还需要一个NestedScrollView

public class NestedScrollView extends FrameLayout implements NestedScrollingParent3,
        NestedScrollingChild3, ScrollingView 

即可以当父View也可以当子View

下面我们来看下NestedScrollView包裹着recyclerView的情况下嵌套滑动如何处理

当两者嵌套滑动的时候,不需要我们去处理滑动冲突,谷歌已经帮我们处理好了,那么很明显,down事件被recyclerView获取到。

recyclerView的ACTION_DOWN–》 startNestedScroll(nestedScrollAxis, TYPE_TOUCH);–》onStartNestedScroll(找到支持嵌套滑动的父View)–》onNestedScrollAccepted(记录相关信息)

recyclerView的ACTION_MOVE–》dispatchNestedPreScroll(滑动之前先问一下父View你滑不滑动)下面会触发父View

NestedScrollLayout 的onNestedPreScroll方法

按照我们上面一开始的分析,接下来就在父View的onNestedPreScroll方法里处理滑动逻辑就OK了,但是NestedScrollLayout即是父View也子View,所以它又调用了dispatchNestedPreScroll方法,它没有滑动。

当我们需要它先滑动一部分的时候怎么办?

继承,重写对应的方法即可:
topView是recyclerView上面NestedScrollLayout里面的一块布局

 @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        Log.i("NestedScrollLayout", getScrollY()+"::onNestedPreScroll::"+topView.getMeasuredHeight());
        // 向上滑动。若当前topview可见,需要将topview滑动至不可见
        boolean hideTop = dy > 0 && getScrollY() < topView.getMeasuredHeight();
        if (hideTop) {
            scrollBy(0, dy);
            //告诉子View我滑了多少
            consumed[1] = dy;
        }
    }

当我们设置(默认)是惯性滑动的时候会调用

 @Override
    public void fling(int velocityY) {
        super.fling(velocityY);
        if (velocityY <= 0) {
            this.velocityY = 0;
        } else {
            isStartFling = true;
            this.velocityY = velocityY;
        }
    }

根据自己判断,当我滑完了还剩下的多少没滑完传递给子View

private void dispatchChildFling() {
        if (velocityY != 0) {
        	//velocityY滑动速度转换成距离
            Double splineFlingDistance = mFlingHelper.getSplineFlingDistance(velocityY);
            if (splineFlingDistance > totalDy) {
            	//将距离减去我滑去剩下再转成速度,因为对方只支持接收速度的变量
                childFling(mFlingHelper.getVelocityByDistance(splineFlingDistance - 
                Double.valueOf(totalDy)));
            }
        }
        totalDy = 0;
        velocityY = 0;
    }
 private void childFling(int velY) {
 		//找到我的子View中的recyclerView
        RecyclerView childRecyclerView = getChildRecyclerView(contentView);
        if (childRecyclerView != null) {
        	//把速度传给它
            childRecyclerView.fling(0, velY);
        }
    }

可能很多朋友都知道 CoordinatorLayout天然就能支持这种实现,为什么还要花时间去研究这个呢,其实 CoordinatorLayout内部的滑动原理核心就是这个,下一篇我们就来研究 CoordinatorLayoutbehavior

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

嵌套滑动学习 的相关文章

  • 无法解析插件 Java Spring

    我正在使用 IntelliJ IDEA 并且我尝试通过 maven 安装依赖项 但它给了我这些错误 Cannot resolve plugin org apache maven plugins maven clean plugin 3 0
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • 如何将 pfx 文件转换为 jks,然后通过使用 wsdl 生成的类来使用它来签署传出的肥皂请求

    我正在寻找一个代码示例 该示例演示如何使用 PFX 证书通过 SSL 访问安全 Web 服务 我有证书及其密码 我首先使用下面提到的命令创建一个 KeyStore 实例 keytool importkeystore destkeystore
  • 在 Mac 上正确运行基于 SWT 的跨平台 jar

    我一直致力于一个基于 SWT 的项目 该项目旨在部署为 Java Web Start 从而可以在多个平台上使用 到目前为止 我已经成功解决了由于 SWT 依赖的系统特定库而出现的导出问题 请参阅相关thread https stackove
  • Google App Engine 如何预编译 Java?

    App Engine 对应用程序的 Java 字节码使用 预编译 过程 以增强应用程序在 Java 运行时环境中的性能 预编译代码的功能与原始字节码相同 有没有详细的信息这是做什么的 我在一个中找到了这个谷歌群组消息 http groups
  • 无法捆绑适用于 Mac 的 Java 应用程序 1.8

    我正在尝试将我的 Java 应用程序导出到 Mac 该应用程序基于编译器合规级别 1 7 我尝试了不同的方法来捆绑应用程序 1 日食 我可以用来在 Eclipse 上导出的最新 JVM 版本是 1 6 2 马文 看来Maven上也存在同样的
  • 我的设备突然没有显示在“Android 设备选择器”中

    我正在使用我的三星 Galaxy3 设备来测试过去两个月的应用程序 它运行良好 但从今天早上开始 当我将设备连接到系统时 它突然没有显示在 Android 设备选择器 窗口中 我检查过 USB 调试模式仅在我的设备中处于选中状态 谁能猜出问
  • 如何从终端运行处理应用程序

    我目前正在使用加工 http processing org对于一个小项目 但是我不喜欢它附带的文本编辑器 我使用 vim 编写所有代码 我找到了 pde 文件的位置 并且我一直在从 vim 中编辑它们 然后重新打开它们并运行它们 重新加载脚
  • .isProviderEnabled(LocationManager.NETWORK_PROVIDER) 在 Android 中始终为 true

    我不知道为什么 但我的变量isNetowrkEnabled总是返回 true 我的设备上是否启用互联网并不重要 这是我的GPSTracker class public class GPSTracker extends Service imp
  • 如何根据 gradle 风格设置变量

    我想传递一个变量test我为每种风格设置了不同的值作为 NDK 的定义 但出于某种原因 他总是忽略了最后味道的价值 这是 build gradle apply plugin com android library def test andr
  • 如何在桌面浏览器上使用 webdriver 移动网络

    我正在使用 selenium webdriver 进行 AUT 被测应用程序 的功能测试自动化 AUT 是响应式网络 我几乎完成了桌面浏览器的不同测试用例 现在 相同的测试用例也适用于移动浏览器 因为可以从移动浏览器访问 AUT 由于它是响
  • Android 套接字和 asynctask

    我即将开始制作一个应该充当 tcp 聊天客户端的应用程序 我一直在阅读和阅读 我得出的结论是最好 如果不需要 将我的套接字和异步任务中的阅读器 问题是我不确定从哪里开始 因为我是 Android 新手 这至少对我来说是一项艰巨的任务 但据我
  • 获取 JVM 上所有引导类的列表?

    有一种方法叫做findBootstrapClass对于一个类加载器 如果它是引导的 则返回一个类 有没有办法找到类已经加载了 您可以尝试首先通过例如获取引导类加载器呼叫 ClassLoader bootstrapLoader ClassLo
  • 一次显示两条Toast消息?

    我希望在一个位置显示一条 Toast 消息 并在另一位置同时显示另一条 Toast 消息 多个 Toast 消息似乎总是按顺序排队和显示 是否可以同时显示两条消息 是否有一种解决方法至少可以提供这种外观并且不涉及扰乱活动布局 Edit 看来
  • 如何在Xamarin中删除ViewTreeObserver?

    假设我需要获取并设置视图的高度 在 Android 中 众所周知 只有在绘制视图之后才能获取视图高度 如果您使用 Java 有很多答案 最著名的方法之一如下 取自这个答案 https stackoverflow com a 24035591
  • 实现滚动选择 ListView 中的项目

    我想使用 ListView 您可以在其中滚动列表来选择一个项目 它应该像一个 Seekbar 但拇指应该是固定的 并且您必须使用该栏来调整它 我面临的一个问题是 我不知道这种小部件是如何调用的 这使得我很难搜索 所以我制作了下面这张图片 以
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • 当我从 Netbeans 创建 Derby 数据库时,它存储在哪里?

    当我从 netbeans 创建 Derby 数据库时 它存储在哪里 如何将它与项目的其余部分合并到一个文件夹中 右键单击Databases gt JavaDB in the Service查看并选择Properties This will
  • 如何修复 JNLP 应用程序中的“缺少代码库、权限和应用程序名称清单属性”?

    随着最近的 Java 更新 许多人都遇到了缺少 Java Web Start 应用程序的问题Codebase Permissions and Application name体现属性 尽管有资源可以帮助您完成此任务 但我找不到任何资源综合的
  • Spring Boot @ConfigurationProperties 不从环境中检索属性

    我正在使用 Spring Boot 1 2 1 并尝试创建一个 ConfigurationProperties带有验证的bean 如下所示 package com sampleapp import java net URL import j

随机推荐

  • Python使用SQLAlchemy

    Python使用SQLAlchemy 1 安装SQLAlchemy 备注 本文适用于SQLAlchemy gt 2 0 安装SQLAlchemy pip install SQLAlchemy 安装pymysql pip install py
  • SciPy 用户指南

    1 介绍 1 1 SciPy Organization SciPy 被组织成涵盖不同科学计算领域的子包 这些总结在下表中 分包 描述 cluster 聚类算法 constants 物理和数学常数 fft 快速傅里叶变换 integrate
  • c++之模板

    目录 一 函数模板 1 函数模板的格式 2 函数模板用法举例 二 类模板 1 类模板的格式 2 类模板用法举例 在C 中涉及了一个新知识 模板 关于模板 它是代码复用的手段 是泛型编程的基础 这里会介绍函数模板和类模板两种 一 函数模板 函
  • Python matplotlib 画图窗口显示到gui或者控制台的方法

    我们再用Jupyter notebook ipython console qtconsole的时候 有的时候画图希望不弹出窗口 直接画在console里 又得时候有希望弹出窗口 因为console里太小了 那么我们可以用下面的命令 matp
  • Python画樱花树的代码

    不废话 直接上代码 import turtle import random def draw sakura branch len if branch len gt 3 if 8 lt branch len lt 12 if random r
  • 【概率论】离散型随机变量分布——伯努利分布、泊松分布

    先简单复习下之前的内容 离散型随机变量指的是随机变量X的取值是有限的 或无穷可列的 详细的解释可以参照这篇博文 https blog csdn net dengfangmei1216 article details 107526615 随机
  • CSDN-markdown编辑器使用

    欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来 用它写博客 将会带来全新的体验哦 Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和图片上传 LaTex数学公式 UML序列
  • 微信二次分享解决方案

    最近项目中开发需要在微信端二次分享分享H5页面 但是第一次分享的时候安卓没有问题 ios有时成功有时失败 并且二次分享的时候安卓还是没有问题 ios一次成功都没有 后来查阅文档搜索资料终于解决了 一下是解决资料内容 百度上自定义微信分享标题
  • Nmap网络扫描

    目录 预备知识 TCP与UDP 扫描的分类 nmap简介 实验目的 实验环境 实验步骤一 任务描述 安装Nmap 实验步骤二 任务描述 Zenmap基本应用 实验步骤三 任务三 nmap命令行的使用 预备知识 TCP与UDP TCP是一种面
  • SpringBoot+MyBits 调用mybatis-config.xml的方法

    SpringBoot MyBits 调用mybatis config xml的方法 在application properties中如做下调用 Mybatis mybatis config location classpath mybati
  • 已解决:Java环境变量配置后不生效

    一 问题 从jdk8升级到jdk11 配置JAVA HOME后 不生效 备注 jdk8是安装版 jdk11是解压版 二 解决办法 在环境变量Path中 删除下面的配置 C Program Files x86 Common Files Ora
  • Python 的reload()方法

    reload 函数将以前导入过的模块再加载一次 重新加载 reload 包括最初导入模块时应用的分析过程和初始化过程 这样就允许在不退出解释器的情况下重新加载已更改的Python模块 若干注意事项 1 如果模块在语法上是正确的 但在初始化过
  • 攻防世界_Crypto_sherlock

    攻防世界刷题记录Crypto篇 文章目录 攻防世界刷题记录Crypto篇 前言 解题步骤 1 筛选出文中的大写字母 2 借助Python处理字符串 总结 前言 继续高手进阶区题目 sherlock 咦 夏洛克 下载题目附件得到的是一个内容很
  • Docker root用户的pip使用方法

    Docker下root用户 pip install XX 显示pip命令不存在 原始目标 pip install XX pip install root user action ignore XX 要安装的包 参考 WARNING Runn
  • JavaScript中的正则表达式

    ECMAScript 通过RegExp类型来支持正则表达式 测试的方法 pattern test str 或 pattern exec str 其中str 是待匹配的字符串 pattern 是正则表达式 JavaScript 中的正则表达式
  • 找不到msvcp140.dll无法继续执行代码怎么解决?分享三个解决方法

    当你在运行某个程序或游戏时遇到msvcp140 dll缺失的错误提示 你可能会感到困惑和烦恼 在修复msvcp140 dll的过程中 我遇到了一些挑战 但最终成功解决了这个问题 以下是我总结的三个解决方法 希望能帮助你解决这个问题 找不到m
  • sklearn中的归一化方法StandardScaler中的fit、transform和fit_transform

    StandardScaler类 常用的数据归一化方式 减去平均值 然后通过标准差映射到均至为0的空间内 系统会记录每个输入参数的平均数和标准差 以便数据可以还原 sklearn preprocessing StandardScaler能够轻
  • 【极速版】vite安装配置ceisum教程

    Hello大家好 今天带来的是vite版本的cesium的安装教程 借助一个国人大佬写的插件vite plugin cesium 安装非常简单快捷 一起来看看吧 1 安装vite插件 官网 https github com nshen vi
  • opencv遍历彩色图像、灰度图像像素值的方法

    https www bbsmax com A RnJWZb2ozq
  • 嵌套滑动学习

    我们知道两个同一方向的可以滑动的View 如果不做任何处理 会出现滑动冲突 处理滑动冲突我们有内部和外部拦截法 如果有朋友还不知道事件分发的原理可以看包括滑动冲突的拦截事件分发源码的学习分享 处理滑动冲突之后还是存在缺陷 比如一次滑动操作父