NestedScrollView嵌套滑动原理
事件分发机制简介
其实小编应该在前面准备一章关于Android的事件分发和处理机制的专题,因为时间原因和内容源码比较多吧(小编有强迫症,不想不贴源码),不过还是因为小编懒,哈哈。这里就对事件分发机制做一个简单的回忆,大家感兴趣的可以去看看相关资料和源码,小编后面有空也会出一期。
这里介绍一位优秀博主的博客,大家可以看这篇文章对事件分发机制与滑动冲突、以及解决方案有个全面的了解:
下面对事件分发与处理做一个简单总结
关键的一点大家需要记住:事件的分发是由上层到底层,而事件的处理是由底层到上层。
- 也就是说父View可以拦截触摸事件直接消费,而不传递给子View。
- 子View可以处理事件,也可以选择给父类消费。
- ViewGroup既可以消费事件,也可以分发事件
- View只能处理事件,也就是消费。
View滑动冲突
- 外部滑动方向与内部滑动方向不一致
解决方案:外部拦截法,当事件传递到父View时,父View需要处理此事件,就拦截,不处理此事件就不拦截
- 外部滑动方向与内部滑动方向一致;
内部拦截法,当事件传递到父View时,父View都传递给子View,如果子View需要处理此事件就消耗掉,否则就交给父View处理。但是这种方法和Android事件分发不一致,需要配合 requestDisallowInterceptTouchEvent 方法才能正常工作
- 上面两种情况的嵌套。
某个View一旦决定拦截,那么这一个事件序列都只能由它来处理,并且它的onInterceptTouchEvent不会再被调用
解决方案: 解决这种滑动冲突,可以用NestedScrollingParent 和 NestedScrollingChild 来解决
分析NestedScrollView机制
NestedScrollView和ScrollView类似,是一个支持滚动的控件。此外,它还同时支持作NestedScrollingParent或者NestedScrollingChild进行嵌套滚动操作。默认是启用嵌套滚动的
首先介绍两个重要接口
1.NestedScrollingParent
当可滑动的ViewGroup充当父View时实现该接口
2.NestedScrollingChild
当可滑动的ViewGroup充当子View时实现该接口
我们所熟知的RecyclerView就对上面两个接口都进行了实现,所以它才能完美的处理滑动与点击item之间的关系,同时RecyclerView还支持惯性滑动(快速滑->手指松开->滑动一段距离停下),这都是上面两个接口中封装的方法的功劳。小编后面展示的demo就比较拉了,没有实现惯性滑动效果,别问!问就是因为懒。
整个事件流程
这张图还是很清晰的展示了从点击->滑动->松开的方法调用过程
NestedScrollingChildHelper、NestedScrollingParentHelper是Google提供的帮助类,其内部是去调用ViewCompat中的方法调用(xxxCompat都是兼容处理),也就是说很多操作都可以由helper帮我们完成,对于小编这种懒人还是很友好的。
下面对方法进行介绍:
onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) 当用户手指按下的时候,你是否要处理这次操作。当你确定要处理这次操作的时候,返回true;如果返回false的时候,就不会去响应后面的回调事件了。你想怎么滑就怎么话,我都不做处理。这里的(axes)滚动方向很重要,可以通过此参数判断滚动方向!
参数3:直接目标,相当于能滑动的控件
参数4:观察的View
参数5:这个可以简单理解为滚动方向
- ViewCompat#SCROLL_AXIS_HORIZONTAL 水平方向
- ViewCompat#SCROLL_AXIS_VERTICAL 竖直方向
参数6:这个参数是之后有的,如果你输入的类型不是TYPE_TOUCH那么就不会相应这个滚动
onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type) 当onStartNestedScroll准备处理这次滑动的时候(返回true的时候),回调这个方法。可以在这个方法中做一些响应的准备工作!
onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type) 当滚动开始执行的时候回调这个方法。
- 参数4/参数5:用户x/y轴滚动的距离(注意这里是每一次都回调的啊!!!)
- 参数6:处理滚动的距离的参数,内部维护着输出距离,假设用户滑动了100px,child 做了90px的位移,你需要把consumed[1]的值改成90,这样coordinatorLayout就能知道只处理剩下的10px的滚动。其中consumed[0]代表x轴、consumed[1]代表y轴。可能你不理解这个问题,换个形象点的比喻,比如你开发某一个功能,但是你只会其中的90%那么怎么办呢?不能就不管了。好你找到了你的同事或者老大,让他去完成剩下的10%。这样问题就完美的解决了,是一个概念的!
onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) 上面这个方法结束的时候,coordinatorLayout处理剩下的距离,比如还剩10px。但是coordinatorLayout发现滚动2px的时候就已经到头了。那么结束其滚动,调用该方法,并将coordinatorLayout处理剩下的像素数作为参
(dxUnconsumed、dyUnconsumed)
传过来,这里传过来的就是 8px。参数中还会有coordinatorLayout处理过的像素数(dxConsumed、dyConsumed)。老大开始处理剩下的距离了!这个方法主要处理一些越界后的滚动。还是不懂对吧!还拿你们老大做比喻:比如上面还剩 10%的工作,这时老大处理了2%后发现已经可以上线了,于是老大结束了工作,并将处理剩下的内容(dxUnconsumed、dyUnconsumed)纪录下来,告诉你。老大处理了的内容(dxConsumed、dyConsumed)也告诉了你。- 参数4/参数5:当没有滚动到顶部或者底部的时候,x/y轴的滚动距离
- 参数6/参数7:当滚动到顶部或者底部的时候,x/y轴的滚动距离
onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY) 当手指松开发生惯性动作之前调用,这里提供了响应的速度,你可以根据速度判断是否需要进行折叠等一系列的操作,你要确定响应这个方法的话,返回true。
- 参数4/参数5:代表相应的速度
onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) 停止滚动的时候回调的方法。当你不去响应Fling的时候会直接回调这个方法。在这里可以做一些清理工作。或者其他的内容。
再来看看两个接口的源码
1.NestedScrollingChild
public interface NestedScrollingChild { |
public interface NestedScrollingChild2 extends NestedScrollingChild { |
public interface NestedScrollingChild3 extends NestedScrollingChild2 { |
2.NestedScrollingParent
public interface NestedScrollingParent { |
public interface NestedScrollingParent2 extends NestedScrollingParent { |
public interface NestedScrollingParent3 extends NestedScrollingParent2 { |
Demo
在那之前再补一张较全的事件流程图,还包括传值的过程欧,结合demo代码看也不错。
效果演示
这里就不贴大量代码了,展示一些主要代码,详情可见文章下方的全部代码
总结
只要对NestedScrollView的嵌套滑动机制过程掌握,利用NestedScrollingParent、NestedScrollingChild这两个接口可以很轻松的实现自定义嵌套滑动机制,当然也可以直接通过继承自定义NestedScrollView实现一些炫酷的滑动、吸顶效果,这种效果在现在的电商平台还是很常见的。当然仅仅利用NestedScrollingParent、NestedScrollingChild实现嵌套还是有局限,只能一对一,要想实现一对多的嵌套滑动那就需要Behavior,下一章将会初步接触。