Android嵌套滑动机制分析

温馨提示:Android事件分发机制是Android嵌套滑动机制的基石,阅读本文前请务必保证熟练掌握Android事件分发机制。传送门->Android事件分发机制

本文大纲

1. 嵌套ScrollView同向滑动

2. 传统事件分发和嵌套滑动事件分发

3. NestedScrollingChild和NestedScrollingParent

4. NestedScrollingChildHelper和NestedScrollingParentHelper

5. 结合案例讲解嵌套滑动事件分发顺序

1. 嵌套ScrollView同向滑动

所谓嵌套ScrollView同向滑动,是指两个可滑动的View内外嵌套,并且他们的方向是相同的。
嵌套ScrollView
当在内部ScrollView中上下滑动时,会有两种情况。

  1. 外部ScrollView优先获取上下滑动的权力,在蓝色区域上下滑动,内部ScrollView并不会上下滑动非嵌套滑动.gif

  2. 内部的ScrollView优先获取上下滑动的权力,在蓝色区域内上下滑动,内部ScrollView上下滑动嵌套滑动.gif

在Android未提供嵌套滑动机制之前,嵌套ScrollView同向滑动的效果是情况一,外部的ScrollView优先获得上下滑动的权力。原因如下:

当外部ScrollView嵌套内部ScrollView时,DOWN事件在内部ScrollView的onTouchEvent中返回true,内部ScrollView处理了DOWN事件。MOVE事件首先到达外部ScrollView的onInterceptTouchEvent方法,当滑动距离大于mTouchSlop时,会拦截掉MOVE事件,给内部ScrollView发出CANCEL事件,从而外部ScrollView获得了事件的处理权,内部ScrollView失去了事件的处理权。
SrollView#onInterceptTouchEvent
这种效果显然不能满足用户的需求。内部的ScrollView永远无法获得上下滑动的机会。用户希望在内部ScrollView的区域上下滑动时,内部ScrollView优先获得事件的分发权力。Android嵌套滑动机制,很好的解决了ScrollView嵌套时内部ScrollView无法优先处理滑动事件的问题。

2. 传统事件分发和嵌套滑动事件分发

传统事件分发在Android事件分发之树的深度遍历分析法中已经详细讲解过,这里不做详细的讲解。事件会调用父控件的onInterceptTouchEvent方法,判断是否拦截事件。换言之传统的事件分发,生杀大权掌握在父控件上,只有父控件不拦截事件,子控件才有处理事件的机会。

传统事件分发树形图如下传统事件分发树形图

而嵌套滑动事件分发则区别于传统事件分发,事件分发的生杀大权并不掌握在父控件上。当DOWN事件传递到NestedScrollingChild时,NestedScrollingChild控件会向上寻找愿意响应后代View发出嵌套滑动请求的NestedScrollingParent控件,告诉NestedScrollingParent控件,后续的MOVE事件,不要再拦截,事件优先交给NestedScrollingChild处理,如果MOVE滑动超过mTouchSlop阈值,NestedScrollingChild向上请求"不拦截事件"->“parent.requestDisallowInterceptTouchEvent(true)”,从此NestedScrollingChild的所有父控件,对事件全部不拦截,直达NestedScrollingChild控件。

嵌套滑动事件分发树形图如下嵌套滑动事件分发树形图
总结:传统事件分发,父View会优先获取事件的分发权,并且如果事件被某个View处理了,那么它的父View无法获得事件分发的机会。嵌套滑动事件分发,NestedScrollingChild处理DOWN事件时,会告诉它的NestedScrollingParent,后续的MOVE、UP等事件,NestedScrollingParent不拦截,直接交由NestedScrollingChild处理,而且它的父View有机会再次获得事件分发的机会。

3. 嵌套滑动接口

嵌套滑动机制提供了NestedScrollingChild和NestedScrollingParent接口。
NestedScrollingChild

NestedScrollingChild方法以及作用

  1. 控制嵌套滑动机制是否开启->NestedScrollingChild#setNestedScrollingEnable和NestedScrollingChild#isNestedScrollingEnable。

  2. 通知NestedScrollingParent不拦截MOVE、UP等事件->NestedScrollingChild#startNestedScroll。

  3. 处理滑动事件之前,将滑动事件的处理权交由NestedScrollingParent处理->(NestedScrollingChild#dispatchNestedPreScroll)。(这里叨唠一下,子View虽然优先获取了事件分发的权力,但是在滑动之前,还是会把事件交给父View去处理。传统事件分发,父View如果获取了分发权,子View就无法获取分发权。滑动嵌套即使子View获取了分发权,它还是会征询父View,是否要处理掉事件的一部分滑动,父View可以选择处理或者不处理,子View再处理事件剩余滑动距离)。

  4. NestedScrollingChild处理完滑动事件后,如果有剩余的滑动距离没有处理掉,会交给NestedScrollingParent去处理剩余的滑动距离->NestedScrollingChild#dispatchNestedScoll,如果NestedScrollingParent处理了剩余的距离,NestedScrollingChild会校正mLastMotionY。

  5. dispatchNestedPreFling方法与dispatchNestedPreScroll方法类似。当手指离开屏幕,NestedScrollingChild会将Fling的处理权先交给NestedScrollingParent处理。

  6. dispatchNestedFling方法与dispatchNestedScroll方法类似。如果有剩余的滑动Fling距离没有处理掉,会交给NestedScrollingParent去处理剩余的Fling距离

NestedScrollingChild事件处理步骤

  1. 获取事件的优先处理权-> 调用startNestedScroll

  2. 处理滑动之前先问NesteScrollingParent要不要处理一部分距离

  3. NesteScrollingParent处理完之后,NestedScrollingChild再来处理剩余的距离4.NestedScrollingChild处理完之后还有剩余的距离,交给NesteScrollingParent去处理

NestedScrollingParent

NestedScrollingParent接口只能被ViewGroup实现。View实现该接口没有意义。NestedScrollingParent主要有两个作用。作用一、响应NesteScrollingChild的嵌套滑动请求,配合NesteScrollingChild完成嵌套滑动机制的交互。作用二、处理NestedScrollingChild滑动前和滑动后传递过来的事件剩余距离。

NestedScrollingParent方法以及作用

  1. getNestedScrollAxes返回嵌套滑动的方向,垂直方向或者水平方向。NestedScrollingParent正是通过该标志位,决定要不要拦截MOVE事件

  2. onStartNestedScroll():Boolean方法与NestedScrollingChild#startNestedScroll方法相对应。如果返回true表示NestedScrollingParent响应NestedScrollingChild的嵌套滑动,反之表示不响应嵌套滑动。一般做法是外部ScrollView滑动方向和内部ScrollView滑动方向一致返回true。

  3. onNestedScrollAccepted在onStartNestedScroll返回true时,会被调用,该方法体会给mNestedScrollAxes赋值。

  4. onNestedPreScroll方法会在NestedScrollingChild正式处理滑动事件之前调用。

  5. onNestedScroll方法会在NestedScrollingChild正式处理滑动事件之后调用

  6. onNestedPreFling与onNestedPreScroll类似

  7. onNesteFling与onNestedScoll类似

  8. onStopNestedScroll会将mNestedScrollAxes置为0

4. NestedScrollingChildHelper和NestedScrollingParentHelper

帮助类方法比较多,实现类嵌套滑动的核心流程的具体算法。刚接触难免觉得容易混淆。本章不做具体代码讲解,请自行查阅源码。
NestedScrollingChildHelper类是NesteScrollingChild的帮助类。

NestedScrollingChildHelper

NestedScrollingParentHelper
NestedScrollingChildHelper#startNestedScroll。从父View开始向祖先View遍历,寻找到第一个愿意响应嵌套滑动的NestedScrollingParent。
NestedScrollingChildHelper#dispatchNestedPreScroll。如果NestedScrollingParent消耗了部分滑动距离,返回true,否则返回false。

5.结合案例讲解嵌套滑动事件分发顺序

嵌套滑动.gif
上述效果代码如下

自定义的ScrollView,主要是在相关事件方法中加入日志打印
自定义ScrollView

Activity代码Activity代码

布局文件
布局文件

我们把布局文件转化成树形图
布局文件树形图

关于树中的节点,介绍如下
树中节点

日志打印如下
日志

OuterScrollView拦截MOVE事件条件:滑动距离>mTouchSlop&&OuterScrollView未响应嵌套滑动机制。

  1. DOWN事件到达OuterScrollView#onInterceptTouchEvent,不拦截。

  2. DOWN事件到达InnerScrollView#onInterceptTouchEvent,接着调用InnerScrollView#startNestedScroll,触发OuterScrollView#onStartNested,接着调用InnerScrollView#dispatchTouchEvent,由于是DOWN事件,会调用View#dispatchTouchEvent,调用stopNestedScroll,触发OuterScrollView#onStopNestedScroll。

  3. DOWN事件到达InnerScrollView#onTouchEvent方法,调用startNestedScroll,触发OuterScrollView#onStartNestedScroll,OuterScrollView响应嵌套滑动机制。

  4. MOVE事件到达OuterScrollView#onInterceptTouchEvent,拦截条件不成立,不拦截。

  5. MOVE事件到达InnerScrollView#onTouchEvent,调用InnerScrollView#dispatchNestedPreScroll,由于dy=0 dx=0,不会触发OuterScrollView的onNestedPreScroll。

  6. MOVE事件到达OuterScrollView#onInterceptTouchEvent,拦截条件不成立,不拦截。

  7. MOVE事件到达InnerScrollView#onTouchEvent,dy<mTouchSlop,只会触发InnerScrollView#dispatchNestedPreScroll,不会触发dispatchNestedScroll。

  8. MOVE事件到达OuterScrollView#onInterceptTouchEvent,拦截条件不成立,不拦截。

  9. MOVE事件到达InnerScrollView#onTouchEvent,dy<mTouchSlop,只会触发InnerScrollView#dispatchNestedPreScroll,不会触发dispatchNestedScroll。

  10. MOVE事件到达OuterScrollView#onInterceptTouchEvent,拦截条件不成立,不拦截。

  11. MOVE事件到达InnerScrollView#onTouchEvent,dy<mTouchSlop,只会触发InnerScrollView#dispatchNestedPreScroll,不会触发dispatchNestedScroll。

  12. MOVE事件到达OuterScrollView#onInterceptTouchEvent,拦截条件不成立,不拦截。

  13. MOVE事件到达InnerScrollView#onTouchEvent,(dy=31)>mTouchSlop,会同时触发InnerScrollView#dispatchNestedPreScroll和dispatchNestedScroll,同时会调用parent.requestDisallowInterceptTouchEvent(true)。从此MOVE事件直达InnerScrollView#onTouchEvent

  14. MOVE事件直达InnerScrollView#onTouchEvent

  15. MOVE事件直达InnerScrollView#onTouchEvent

  16. 处理UP事件

总结DOWN事件和MOVE事件在NestedScrollingChild中的分发逻辑

  1. DOWN事件到达NestedScrollingChild#onInterceptTouchEvent方法会调用startNestedScroll方法。

  2. DOWN事件到达NestedScrollingChild#dispathTouchEvent方法会调用stopNestedScroll方法。

  3. MOVE事件到达NestedScrollingChild#onTouchEvent方法,调用dispatchNestedPreScroll方法。如果mIsBeingDragged为true。会调用dispatchNestedScroll方法。从非dragged状态变成dragged状态时,会调用ViewParent#requestDisallowInterceptTouchEvent(true)。


  • 欢迎扫码关注公众号

公众号

  • 欢迎扫码加微信好友
    微信好友
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值