专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
开发者全社区  ·  换了个新领导,叫我半年内不要离职。然而这半年 ... ·  6 小时前  
开发者全社区  ·  阿里员工:今天被主管谈话了,给我两个选择:主 ... ·  11 小时前  
开发者全社区  ·  突然崩了!很多人以为手机坏了!官方紧急回应 ·  昨天  
开发者全社区  ·  捡漏清华刷屏,这也行? ·  昨天  
开发者全社区  ·  小作文频出!某基金经理晕厥 ·  2 天前  
51好读  ›  专栏  ›  郭霖

Material Design中的CoordinatorLayout和Behavior详解

郭霖  · 公众号  · android  · 2016-09-21 07:30

正文

今日科技快讯


919是乐视一年一度的狂欢节,就在919狂欢节晚会上,乐视汽车宣布已经完成了10.8亿美元的首轮融资。第一轮融资就如此大手笔,看来乐视汽车未来的路会好走许多,至少短期内是不差钱了。


关于国行版三星Note 7爆炸事件又有了新的进展,三星发表公告称,国行版的Note 7电池不存在问题,手机损坏是由于外部加热所导致的。加上之前有网友爆料称这次事件有友商恶意抹黑的可能,真相变得更加扑朔迷离。不过,不管是在国内还是国外,三星都在经历一个前所未有的信任危机。


作者简介


本篇来自 leon 的投稿,阐述了 嵌套滚动机制 以及 CoordinatorLayout.Behavior ,文章讲得很清楚了,我就不多说了,大家赶快进入正文吧~


leon 的博客地址:

http://ltlovezh.com


嵌套滚动机制


本文将介绍下 CoordinatorLayout 是如何协调 子View 间关系的。在介绍 CoordinatorLayout 之前,首先需要了解下 嵌套滚动机制 NestedScrolling )。


所谓嵌套滚动其实就是界面布局中包含 一个可滚动的列表 一个不可滚动的View ,这样在滚动列表时,首先将不可滚动View移出屏幕或移进屏幕,待不可滚动View固定时,才会继续滚动滚动列表的内容。


为什么会有嵌套滚动机制?


之前我们处理 Touch事件 时,主要通过重写 View dispatchTouchEvent、onInterceptTouchEvent onTouchEvent 等方法处理滚动事件。但这种事件处理方式有一个痛点:


  • Touch事件 要么被 父View 处理,要么被 子View 处理,很难在两者之间协调处理。


也就是说:一旦 子View 决定处理 Touch 事件 ,那么事件就会一直下发到 子View ,即使 子View 不想处理中间的某个 Touch事件 (返回false),那么 父View 也没办法接着处理这个 Touch事件 了;除非 父View 拦截中间的某个 Touch事件 自己处理,但是一旦拦截了 Touch事件 ,那么后续的 Touch事件 将永远不会下发到 子View 了。


针对这种问题,Android 提供了 NestedScrolling 机制,实现嵌套滚动机制主要依赖四个类:


  1. NestedScrollingChild

  2. NestedScrollingParent

  3. NestedScrollingChildHelper

  4. NestedScrollingParentHelper


一般情况下,滚动列表需要实现 NestedScrollingChild 接口,以支持将滚动事件分发给 父ViewGroup ,相应的, 父ViewGroup 需要实现 NestedScrollingParent 接口,以支持将滚动事件进一步的分发给各个 子View ;而下面的两个类则是进行嵌套滚动的辅助类。


一般实现 NestedScrollingChild 接口的滚动列表会把滚动事件委托给 NestedScrollingChildHelper 辅助类来处理。例如: RecyclerView 实现了 NestedScrollingChild 接口,它内部就会把滚动相关事件委托给 NestedScrollingChildHelper 对象来处理,如下所示:




NestedScrollingChild 的方法有很多,更多的可参见源码,上述摘录的是和嵌套滚动机制相关的四个方法。


当我们滚动 RecyclerView 时, RecyclerView 首先会通过 startNestedScroll 方法通知 父ViewGroup (“我马上要滚动了,是否有兄弟节点要一起滚动?”), 父ViewGroup 会进一步把滚动事件分发给所有 子View (实际是分发给和 子View绑定的Behavior ),感兴趣的 子View 会特别关注,即 Behavior. onStartNestedScroll 方法返回 true


针对这个流程,我们看下代码上的实现, RecyclerView 会在 Down事件 时调用 startNestedScroll 方法,我们看下 NestedScrollingChildHelper .startNestedScroll 方法的实现:




上述方法会找到能够协调处理滚动事件的 父ViewGroup ,然后调用它的 onStartNestedScroll 方法,因为 CoordinatorLayout 实现了 NestedScrollingParent 接口,所以我们看下 CoordinatorLayout .onStartNestedScroll 方法:




上述方法会遍历每一个 子View ,询问它们是否对滚动列表的滚动事件感兴趣,若 Behavior.onStartNestedScroll 方法返回 true ,则表示感兴趣,那么滚动列表后续的滚动事件都会分发到该 子View的Behavio r 。而把 Behavior 绑定到 View 的方法有两种:


  • 在布局文件中通过 app:layout_behavior 属性指定。

  • 在自定义View中通过 @CoordinatorLayout.DefaultBehavior 注解指定,就像 AppBarLayout 那样。


因此,我们可以在自定义的 Behavior.onStartNestedScroll 方法中根据实际情况决定是否对滚动事件感兴趣。


OK,假设 CoordinatorLayout 的某个 子View RecyclerView 的滚动事件感兴趣,接下来 RecyclerView 就会把用户的滚动事件源源不断的分发给之前找到的 父ViewGroup ,然后 父ViewGroup 则进一步分发给感兴趣的 子View 。等到感兴趣的 子View 处理完滚动事件后,若用户的滚动距离没有被消费完,那么 RecyclerView 才有机会处理滚动事件,例如:用户一次性滚动了 10px ,其中 某个View 消费了 8px ,那么 RecyclerView 就只能滚动 2px 了。


针对这个流程,我们看下代码实现, RecyclerView 会在 Move事件 时,计算出滚动距离,然后通过 dispatchNestedPreScroll 方法进行分发,我们看下 NestedScrollingChildHelper.dispatchNestedPreScroll 方法的实现:




该方法的 第3个参数 是一个长度为2的一维数组,用于记录 父ViewGroup(其实是父ViewGroup的子View) 消费的滚动长度,若滚动距离没有用完,则滚动列表处理剩下的滚动距离; 第4个参数 也是一个长度为2的一维数组,用于记录滚动列表本身的偏移量,该参数用于修复用户 Touch事件 的坐标,以保证下一次滚动距离的正确性。这些处理逻辑可参见 RecyclerView Move事件 的代码,此处不再贴代码了。


然后, 父ViewGroup 就会把滚动事件分发给感兴趣的 子View ,因为 CoordinatorLayout 实现了 NestedScrollingParent 接口,所以我们看下 CoordinatorLayou t.onNestedPreScroll 方法:




CoordinatorLayout 的处理很简单,把滚动事件分发给各个 子View Behavior . onNestedPreScroll 方法处理,并计算出最终消费的滚动距离。


因此,我们可以在自定义的 Behavior.onNestedPreScroll 方法中处理 子View 的滚动事件,然后根据实际情况填写消费的滚动距离。


OK,假设 RecyclerView 的滚动距离没有被 CoordinatorLayout 消费完,那么接下来 RecyclerView 应该处理这些滚动事件了。在 RecyclerView onTouchEvent 方法中会调用 scrollByInternal 处理内容滚动,关键代码如下所示:




如上所示, RecyclerView 通过 LayoutManager 处理了剩余的滚动距离,然后计算出对剩余滚动量的消费情况,通过 dispatchNestedScroll 方法继续分发给 CoordinatorLayout ,而 CoordinatorLayout 则通过 onNestedScroll 方法分发给感兴趣的 子View Behavior 处理。这部分的代码逻辑和 onNestedPreScroll 类似,就不贴出了,感兴趣的可以直接看源码。


因此,我们可以在自定义的 Behavior.onNestedScroll 方法中检测到滚动距离的最终消费情况。


OK,现在假设用户结束滚动操作了,即应该结束一系列的滚动事件了, RecyclerView 会在 UP事件 中调用 stopNestedScroll 方法,该方法和上面介绍的三个方法类似,都会先把事件分发给 父ViewGroup ,然后 父ViewGroup 再把事件分到各个 子View ,最终触发 子View Behavior.onStopNestedScroll 方法,感兴趣可以可接看源码,此处不再贴出。







请到「今天看啥」查看全文