NestedScrolling_liyue199512的博客-程序员秘密

技术标签: 嵌套滑动  Android  

NestedScrolling作用

解决嵌套滑动

比ViewGroup事件分发处理的优越性

事件分发:子View首先得到事件处理权,处理过程中,父View可以对其拦截,但是拦截了以后就无法再还给子View(本次手势内)。

NestedScrolling:内部View在滚动的时候,首先将dx,dy交给NestedScrollingParent,NestedScrollingParent可对其进行部分消耗,剩余的部分还给内部View。

事件分发,对于拦截相当于一锤子买卖,只要拦截了,当前手势接下来的事件都会交给Parent(拦截者)来处理。使用ViewGroup事件分发处理实现某些效果,需要手动调用分发事件、手动发出事件。

NestedScrolling原理

嵌套滑动存在两个角色:子View和父View(注:一个View可以同时扮演2个角色,子View并不一定是父View的直接子View)

  1. 子View在收到DOWN事件时,会找到自己祖上中最近的能与自己匹配的父View,与它进行绑定并关闭它的事件拦截机制
  2. 然后子View会在接下来的MOVE事件中判定出用户触发了滑动手势,并把事件流拦截下来给自己消费(注:在NestedScrollView、RecyclerView这类经典实现中,只要用户手指一按下,就会拦截事件流。)
  3. 消费事件流时,对于每一次MOVE事件增加的滑动距离: 
    子View并不是直接自己消费,而是先把它交给父View,让父View可以在子View之前消费滑动 
    如果父View没有消费或是没有消费完,子View再自己消费剩下的滑动 
    如果子View自己还是没有消费完这个滑动,会再把剩下的滑动交给父View消费 
    最后如果滑动还有剩余,子View可以做最终的处理

对每一次MOVE事件传递来的滑动,都使用「parent -> child -> parent -> child」机制进行消费,让子View在消费滑动时与父View配合更加细致、紧密和灵活

NestedScrolling具体接口和调用时机

NestedScrolling 接口分为两个部分:NestedScrollingParent 及 NestedScrollingChild。

NestedScrollingParent

 
  1. public interface NestedScrollingParent {
  2. /**
  3. * 开始NestedScroll时调用
  4. * 对子孙View开始滑动请求的回应(NestedScrollingChild.startNestedScroll)
  5. * 返回true就意味着后面可以接受到NestedScroll事件,否则就无法接收。
  6. *
  7. * @param child 该view的直接子view(包含发起请求的子孙View)
  8. * @param target 发出NestedScroll事件的子孙View,和child不一定是同一个
  9. * @param nestedScrollAxes 滑动的方向,为ViewCompat#SCROLL_AXIS_HORIZONTAL或者ViewCompat#SCROLL_AXIS_VERTICAL,亦或两者都有。
  10. * @return 返回true代表要消耗这个NestedScroll事件,否则就是false。
  11. * */
  12. public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
  13.  
  14. /**
  15. * 在onStartNestedScroll之后调用
  16. * 对开始滑动响应的回调(onStartNestedScroll返回true之后会有此回调产生)
  17. * 参数意义同上
  18. * 使NestedScrollingParent有做滑动初始化工作的时机
  19. * */
  20. public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
  21.  
  22. /**
  23. * 终止nestedscroll的回调(NestedScrollingChild调用stopNestedScroll)
  24. *
  25. * @param target 发出NestedScroll事件的子孙View
  26. * */
  27. public void onStopNestedScroll(View target);
  28.  
  29. /**
  30. * 在target每次滑动之前会调用这个方法
  31. * 在NestedScrollingChild处理滑动之前,预处理此滑动
  32. *
  33. * consumed是一个长度为2的数组。
  34. * 第0位时我们在x方向消耗的滑动距离
  35. * 第1位是我们在y方向上消耗的滑动距离
  36. * 子view会根据这个和dx/dy来计算余下的滑动量,来决定自己是否还要进行剩下的滑动。
  37. * 比如我们使consumed[1] = dy,那么子view在y方向上就不会滑动。
  38. *
  39. * @param target 发出NestedScroll事件的子孙View
  40. * @param dx 这次滑动事件在x方向上滑动的距离
  41. * @param dy 这次滑动事件在y方向上滑动的距离
  42. * @param consumed 回填参数,填入此次预处理消耗掉的滑动距离
  43.  
  44. * */
  45. public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
  46.  
  47. /**
  48. * 在target滑不动的时候会调用这个方法
  49. * 处理NestedScrollingChild未消耗完的滑动距离。
  50. * 如果目标view可以一直滑动,那么这个方法就不会被调用
  51. *
  52. * @param target 发出NestedScroll事件的子孙View
  53. * @param dxConsumed 已消耗的x轴滑动距离
  54. * @param dxUnconsumed 未消耗的x轴滑动距离
  55. * @param dyConsumed 已消耗的y轴滑动距离
  56. * @param dyUnconsumed 未消耗的y轴滑动距离
  57. *
  58. * */
  59. public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
  60. int dxUnconsumed, int dyUnconsumed);
  61.  
  62. /**
  63. * 在target判断为fling并且执行fling之前调用,我们可以通过返回true来拦截目标的fling,这样它就不会执行滑动。
  64. * @param target 发出请求的子孙View
  65. * @param velocityX 在x方向的起始速度
  66. * @param velocityY 在y方向的起始速度
  67. * @return 我们是否消耗此次fling,返回true代表拦截,返回false,目标view就进行正常的fling
  68. * */
  69. public boolean onNestedPreFling(View target, float velocityX, float velocityY);
  70.  
  71. /**
  72. * 在target进行fling后调用。注意这个方法并不是像onNestedScroll在子view滑不动之后调用,而是紧跟着onNestedPreFling后会被调用。因此对于它的使用场景一般比较少。
  73. *
  74. * @param target 目标view
  75. * @param velocityX 在x方向的速度,注意这是fling的起始速度,并不是目标在滑不动时停止时刻的速度,它和onNestedPreFling中的velocityX是一样的。
  76. * @param velocityY 在y方向的速度,注意这是fling的起始速度,并不是目标在滑不动时停止时刻的速度,它和onNestedPreFling中的velocityY是一样的。
  77. * @param consumed 目标view是否消耗了此次fling
  78. * @return 本view是否消耗了这次fling,return true会拦截掉内部View的事件
  79. * */
  80. public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
  81.  
  82. /**
  83. * 获取滑动方向
  84. * SCROLL_AXIS_NONE = 0 没有滑动方向
  85. * SCROLL_AXIS_HORIZONTAL = 1 << 0 横向滑动
  86. * SCROLL_AXIS_VERTICAL = 1 << 1 纵向滑动
  87. * @return 滑动方向
  88. */
  89. public int getNestedScrollAxes();
  90. }

NestedScrollingChild

 
  1. public interface NestedScrollingChild {
  2. /**
  3. * 设置当前View是否启用NestedScroll特性,一般设置为true
  4. * @param enabled 是否启用
  5. */
  6. void setNestedScrollingEnabled(boolean enabled);
  7.  
  8. /**
  9. * 判断当前View是否启用了NestedScroll特性
  10. * @return 是否启用
  11. */
  12. boolean isNestedScrollingEnabled();
  13.  
  14. /**
  15. * 在axes轴上发起NestedScroll开始操作
  16. * @param axes 滑动方向
  17. * @return 是否有NestedScrollingParent接受此次滑动请求,如果不接受返回false,后续的嵌套滑动都将失效
  18. */
  19. boolean startNestedScroll(@ViewCompat.ScrollAxis int axes);
  20.  
  21. /**
  22. * 终止NestedScroll
  23. */
  24. void stopNestedScroll();
  25.  
  26. /**
  27. * 当前是否有NestedScrollingParent接受了此次滑动请求
  28. * @return 是否有NestedScrollingParent接受此次滑动请求
  29. */
  30. boolean hasNestedScrollingParent();
  31.  
  32. /**
  33. * NestedScroll滑动操作中,在自己开始滑动处理之前,分配预处理操作(一般为询问NestedScrollingParent是否消耗部分滑动距离)
  34. * @param dx 当前这一步滑动的x轴总距离(相对于上一次事件,而不是相对于DOWN事件)
  35. * @param dy 当前这一步滑动的y轴总距离
  36. * @param consumed 预处理操作消耗掉的距离(此为输出参数,consumed[0]为预处理操作消耗掉的x轴距离,consumed[1]为预处理操作消耗掉的y轴距离)
  37. * @param offsetInWindow 可选参数,可以为null。为输出参数,获取预处理操作使当前view的位置偏移(offsetInWindow[0]和offsetInWindow[1]分别为x轴和y轴偏移)
  38. * @return 预处理操作是否消耗了部分或全部滑动距离
  39. */
  40. boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
  41. @Nullable int[] offsetInWindow);
  42.  
  43. /**
  44. * 在当前View处理了滑动之后继续分配滑动操作 (一般在自己处理滑动之后,给NestedScrollingParent机会处理剩余的滑动距离)
  45. * @param dxConsumed 已经消耗了的x轴滑动距离
  46. * @param dyConsumed 已经消耗了的y轴滑动距离
  47. * @param dxUnconsumed 未消耗的x轴滑动距离
  48. * @param dyUnconsumed 未消耗的y轴滑动距离
  49. * @param offsetInWindow 可选参数,可以为null。为输出参数,获取预处理操作使当前view的位置偏移(offsetInWindow[0]和offsetInWindow[1]分别为x轴和y轴偏移)
  50. * @return 预处理操作是否消耗了部分或全部滑动距离
  51. */
  52. boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
  53. int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);
  54.  
  55. /**
  56. * 在当前NestedScrollingChild处理fling事件之前进行预处理(一般询问NestedScrollingParent是否处理消耗此次fling)
  57. * @param velocityX x轴速度
  58. * @param velocityY y轴速度
  59. * @return 预处理是否处理消耗了此次fling
  60. */
  61. boolean dispatchNestedPreFling(float velocityX, float velocityY);
  62.  
  63. /**
  64. * 分配fling操作
  65. * @param velocityX x轴方向速度
  66. * @param velocityY y轴方向速度
  67. * @param consumed 当前NestedScrollingChild是否处理了此次fling
  68. * @return NestedScrollingParent是否处理了此次fling
  69. */
  70. boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
  71. }

NestedScrollingParent与NestedScrollingChild接口对应关系

NestedScrollingChild NestedScrollingParent
startNestedScroll onStartNestedScroll
  onNestedScrollAccepted
stopNestedScroll onStopNestedScroll
dispatchNestedScroll onNestedScroll
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedFling onNestedFling
dispatchNestedPreFling onNestedPreFling
  getNestedScrollAxes

调用

针对一次滑动操作,子View接口调用顺序为: 
startNestedScroll -> dispatchNestedPreScroll -> dispatchNestedScroll -> stopNestedScroll

  1. 子View在处理MotionEvent时,决定发起滑动请求,调用startNestedScroll
  2. 调用startNestedScroll会向上逐层遍历父View,调用其onStartNestedScroll接口。如果接口返回true,则此View为与此次NestedScroll联动的父View,中断遍历;返回false则继续向上层遍历直到根View。如果遍历到根View还没找到联动的父View,则后续滑动不可用联动。如果找到了,则进入第3步。(注:以下父View指的是onStartNestedScroll接口返回true的View)
  3. 子View调用父View的onNestedScrollAccepted接口。
  4. 父View的onNestedScrollAccepted接口被调用,做一些滑动初始工作。
  5. 子View探测到用户交互产生了滑动距离,调用父View的onNestedPreScroll接口。
  6. 父View的onNestedPreScroll接口被调用,预处理此次滑动,消耗部分滑动距离(或者不消耗)。
  7. 子View处理剩余的滑动距离。
  8. 如果子View没有处理完剩下的滑动距离,则调用dispatchNestedScroll。
  9. 父View的onNestedScroll被调用,自行决定是否继续处理剩下的滑动距离。
  10. 交互上的滑动终止,子View调用stopNestedScroll。
  11. 父View的onStopNestedScroll被调用。
子View 父View 备注
startNestedScroll onStartNestedScroll、onNestedScrollAccepted 如果onStartNestedScroll返回true,则调用onNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll  
dispatchNestedScroll onNestedScroll  
stopNestedScroll onStopNestedScroll  

版本变化

第一个版本,2014年9月

在 Android 5.0 / API 21 (2014.9) 时, Google 第一次加入了 NestedScrolling 机制。

重构第一个版本,2015年4月

因为第一个版本的 NestedScrolling 机制是加在 framework 层的 View 和 ViewGroup 中,所以能享受到嵌套滑动效果的只能是Android 5.0的系统,也就是当时最新的系统。 所以在当时 NestedScrolling 机制基本没有怎么被使用。 
Google重构出来两个接口(NestedScrollingChild、NestedScrollingParent)两个 Helper (NestedScrollingChildHelper、NestedScrollingParentHelper)外加一个开箱即用的NestedScrollView,在 Revision 22.1.0 (2015.4) 到来之际,把它们一块加入了v4 support library。Revision 22.2.0 (2015.5)时,Google又隆重推出了 Design Support library,其中的杀手级控件CoordinatorLayout更是把 NestedScrolling 机制玩得出神入化。

变化

把 NestedScrolling 机制从 View 和 ViewGroup 中剥离,把有关的 API 放在接口中,把相关实现放在 Helper 里

优点

低版本的 View 也能嵌套滑动

缺点

使用麻烦,暴露了更多内部的不需要普通使用者关心的 API,影响开发者对整个机制的上手速度

第一个版本的Bug

惯性不连续 
在滑动内部 View 时快速抬起手指,内部 View 会开始惯性滑动,当内部 View 惯性滑动到自己顶部时便停止了滑动,此时外部的可滑动 View 不会有任何反应,即使外部 View 可以滑动。

第二个版本,2017年9月

2017年9月,Revision 26.1.0更新了一版NestedScrollingChild2和NestedScrollingParent2,并且处理了第一版中系统控件的Bug,区分开是用户手指移动触发的滑动还是由惯性触发的滑动

第二个版本的Bug

当外部 View 在顶部、内部 View 也在顶部时,往下滑动内部 View 然后快速抬起(制造 fling ),再马上滑外部 View 
预期应该是:外部 View 往上滚动 
但实际上:滑不动它,或是滑上去一点,马上又下来了

第三个版本,2018年11月

2018年11月5日androidx.core 1.1.0-alpha01的更新中,给出了最新的修复——NestedScrollingChild3和NestedScrollingParent3

第三个版本的Bug

当通过滑动内部 View 触发外部 View 滑动时,无法通过触摸外部 View 把它停下来

参考

https://blog.csdn.net/lmj623565791/article/details/52204039 
https://juejin.im/post/5c3c8d2ae51d4552475fcef7 
https://blog.csdn.net/zuguorui/article/details/78671480 
https://blog.csdn.net/tobacco5648/article/details/84667016 
https://blog.csdn.net/dqh147258/article/details/81208889 
https://blog.csdn.net/chen930724/article/details/50307193 
https://blog.csdn.net/lmj121212/article/details/52974427 
https://blog.csdn.net/happy_horse/article/details/54619526 
https://blog.csdn.net/fyfcauc/article/details/52415144

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liyue199512/article/details/89085147

智能推荐

iOS性能优化之内存管理:Analyze、Leaks、Allocations的使用和案例代码_allocations&leaks_zh_2017的博客-程序员秘密

一. 一些相关概念很多人应该比较了解这块内容了...可以权当复习复习...1.内存空间的划分: 我们知道,一个进程占用的内存空间,包含5种不同的数据区:(1)BSS段:通常是存放未初始化的全局变量;(2)数据段:通常是存放已初始化的全局变量。(3)代码段:通常是存放程序执行代码。(4)堆:通常是用于存放进程运行中被动态分配的内存段,OC对象(所有继承自NSObject的对象

【Arduino 无刷电机控制教程】_arduino控制无刷电机_2345VOR的博客-程序员秘密

对于此示例,我有一个具有以下规格的外转子 BLDC 电机:它的 KV 额定值为 1000,可以使用 2S、3S 或 4S 锂聚合物电池供电,并且需要 30A ESC。无刷电机上的 KV 额定值定义了电机在空载时每伏的 RPM。

c语言二维数组入栈出栈,C语言提高 (3) 第三天 二级指针的三种模型 栈上指针数组、栈上二维数组、堆上开辟空间..._weixin_39932611的博客-程序员秘密

1 作业讲解指针间接操作的三个必要条件两个变量 其中一个是指针建立关联:用一个指针指向另一个地址*简述sizeof和strlen的区别strlen求字符串长度,字符数组到’\0’就结束sizeof是看数据类型占用大小(字节何为野指针声明指针变量后,内部数是随机的,为了避免野指针,初始化的时候要设为NULL使用完之后,依然要设为NULL2 昨日回顾3 字符串易犯错误模型判断一个指针是否合法应该看这...

android 滤镜lomo,Android Camera 使用OpenGLES 渲染滤镜以及帧率优化问题_西西nayss的博客-程序员秘密

说到滤镜问题,市面上所有美颜类的相机都存在各式各样的滤镜。那么我们怎么实现滤镜呢?我们首先想到,是否有相关开源项目可以参考的。iOS 下有比较著名的GPUImage是用来做滤镜渲染的,Android下面也有类似的项目。其中,美颜类开源相机比较出名的是程序员杠把子(程序员秘密:http://my.csdn.net/oShunz)的MagicCamera(github地址:https://githu...

从蘑菇街被裁后,我拿到了字节、阿里、拼多多、快手的offer_「已注销」的博客-程序员秘密

写在前面本文不会涉及到具体的面经,更多的像是一篇裁员后的心理历程,并包括我是如何准备简历、准备面试、最终收获满意offer的。如果想要了解我整理的知识点可戳传送门先说说一下自己的情况吧,18届毕业生,以校招生的身份进入蘑菇街。在蘑菇街工作了接近两年时间,蘑菇街4月多进行了裁员,我也是其中之一,目前已经成功入职字节跳动两周多,决定在这个周末写下这段时间的经历,如果其中你有并不认可的观点,欢迎留言讨论。蘑菇街的最后一天我还记得在蘑菇街的最后一天是怎么度过的,像平常一样在公司楼下买了早餐,踩着点坐在了工位

虚幻引擎5快速入门笔记实现创建一个界面 和入门unity对比_吉凶以情迁的博客-程序员秘密

首先吐个槽,陆陆续续看了好多次视频我发现我还是不会虚幻引擎,直到我学会了unity再回来直接看文档,几分钟领悟...论理解的重要性...而且网上都偏向那个材质球玩意为主,文档学习是最快的方法首先 新建选择空白项目image.png在内容管理器新建关卡 此东西等于unity的新建场景或者 菜单新建image.pngimage.png在unity里面...

随便推点

大学英语四六级13年12月大改革应对办法全套复习规划_nf1994的博客-程序员秘密

13年6月的四六级成绩即将揭晓,如果你通过了,你要感到万幸,因为:四六级改革了。。。。大家都愿意瞎蒙的完形填空被取消,大家讨厌的5分翻译题一下子涨到15分,熟悉的快速阅读消失了,句子翻译变成整段翻译,2分一空的阅读选择增大选项难度,阅读增设匹配题等新题型,考试时间增加5分钟,听力的难度有所下降。本日志详细列述一下改革的整体情况,并给出新英语四六级最佳备考方法,内容较长,建议转载保存!日志末尾给出四

[论文阅读笔记65]Template-Based Named Entity Recognition Using BART_happyprince的博客-程序员秘密

1. 基本信息题目论文作者与单位来源年份Template-Based Named Entity Recognition Using BARTLeyang Cui(Zhejiang University),Yu Wu(Microsoft Research Asia),Westlake UniversityACL2021 - Findings202118 Citations, 36 References论文链接: https://aclanthology.org

Android FrameWork底层开发视频_安卓framework开发视频_DroidMind的博客-程序员秘密

=========================== 下面的链接失效了,这里重新给个 链接: https://pan.baidu.com/s/1iZ1SXS4Oy8drjaQOJ2kR4g 密码: 62ua============================= 1.如题,下面是百度云盘链接:https://pan.baidu.com/s/1boDbEIN另一个链接: 探索...

POJ 3767 I Wanna Go Home【最短路floyd】_mengxiang000000的博客-程序员秘密

I Wanna Go HomeTime Limit: 1000MS Memory Limit: 65536KTotal Submissions: 3371 Accepted: 1445DescriptionThe country is facing a terrible civil war----cities in t

U的含义,u是什么单位_单位u代表多少_zxl112358的博客-程序员秘密

“U”是一种表示机架式服务器外部尺寸的单位,是unit的缩略语,详细尺寸由作为业界团体的美国电子工业协会(EIA)决定。 之所以要规定服务器的尺寸,是为了使 服务器保持适当的尺寸以便放在铁质或铝质机架上。机架上有固定服务器的螺孔,将它与服务器的螺孔对好,用螺丝加以固定。 将服务器放置到机架上,并不仅仅有利于日常的维护及管理,也可能避免意想不到的故障。首先,放置服务器不占用过多空间。机架

Loadrunner安装常见问题_loadrunner语言包加载不出来_yifeiyuann的博客-程序员秘密

问:安装loadrunner出现"replace string failed"的提示是什么意思?由于出现了这个,所以loadrunner安装不上,高手帮忙 答:loadrunner安装文件夹的高层所在目录的目录名中最好不要含有中文,比如你把loadrunner是放在某盘的“工具”文件夹中,这样安装就会报错,建议把loadrunner放在任何盘的根目录下安装。

推荐文章

热门文章

相关标签